diff --git a/sample/Assets/Scripts/Passport/AuthenticatedSceneManager.cs b/sample/Assets/Scripts/Passport/AuthenticatedSceneManager.cs index ec47df36e..262029ffd 100644 --- a/sample/Assets/Scripts/Passport/AuthenticatedSceneManager.cs +++ b/sample/Assets/Scripts/Passport/AuthenticatedSceneManager.cs @@ -60,6 +60,6 @@ public static void NavigateToAuthenticatedScene() public static void NavigateToUnauthenticatedScene() { - UnityEngine.SceneManagement.SceneManager.LoadScene("InitialisationWithUI"); + UnityEngine.SceneManagement.SceneManager.LoadScene("UnauthenticatedScene"); } } \ No newline at end of file diff --git a/src/Packages/Passport/Runtime/Scripts/Private/Immutable.Passport.Runtime.Private.asmdef b/src/Packages/Passport/Runtime/Scripts/Private/Immutable.Passport.Runtime.Private.asmdef index 289b573bb..5ca03f58a 100644 --- a/src/Packages/Passport/Runtime/Scripts/Private/Immutable.Passport.Runtime.Private.asmdef +++ b/src/Packages/Passport/Runtime/Scripts/Private/Immutable.Passport.Runtime.Private.asmdef @@ -6,7 +6,8 @@ "UniTask", "Immutable.Browser.Core", "Immutable.Browser.Gree", - "Immutable.Passport.Core.Logging" + "Immutable.Passport.Core.Logging", + "Vuplex.WebView" ], "includePlatforms": [ "Android", diff --git a/src/Packages/Passport/Runtime/Scripts/Private/UI/AndroidPassportWebView.cs b/src/Packages/Passport/Runtime/Scripts/Private/UI/AndroidPassportWebView.cs index 49230e97c..8d0d15a99 100644 --- a/src/Packages/Passport/Runtime/Scripts/Private/UI/AndroidPassportWebView.cs +++ b/src/Packages/Passport/Runtime/Scripts/Private/UI/AndroidPassportWebView.cs @@ -237,7 +237,7 @@ public void Dispose() } } - #region Private Implementation + #region Private Implementation private void CreateWebViewObject() { @@ -248,7 +248,7 @@ private void CreateWebViewObject() UnityEngine.Object.DontDestroyOnLoad(webViewGameObject); // Add WebViewObject component - webViewObject = webViewGameObject.AddComponent(); + webViewObject = new WebViewObject(); PassportLogger.Info($"{TAG} Gree WebViewObject created successfully"); } @@ -276,9 +276,9 @@ private void ConfigureWebView() PassportLogger.Info($"{TAG} Gree WebView configured successfully"); } - #endregion + #endregion - #region WebView Event Handlers + #region WebView Event Handlers private void OnWebViewMessage(string message) { @@ -326,7 +326,7 @@ private void OnWebViewLog(string message) PassportLogger.Debug($"{TAG} WebView console log: {message}"); } - #endregion + #endregion } #endif } diff --git a/src/Packages/Passport/Runtime/Scripts/Private/UI/AndroidVuplexWebView.cs b/src/Packages/Passport/Runtime/Scripts/Private/UI/AndroidVuplexWebView.cs new file mode 100644 index 000000000..526a4bf8e --- /dev/null +++ b/src/Packages/Passport/Runtime/Scripts/Private/UI/AndroidVuplexWebView.cs @@ -0,0 +1,209 @@ +#nullable enable +using System; +using System.Collections.Generic; +using Cysharp.Threading.Tasks; +using UnityEngine; +using UnityEngine.UI; +using Immutable.Passport.Core.Logging; + +#if UNITY_ANDROID && !UNITY_EDITOR && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE +using Vuplex.WebView; +#endif + +namespace Immutable.Passport +{ + /// + /// Android implementation of IPassportWebView using Vuplex WebView + /// Provides embedded WebView functionality within the Unity app (not external browser) + /// This is different from AndroidPassportWebView which uses external browser for auth flows + /// + public class AndroidVuplexWebView : IPassportWebView + { + private const string TAG = "[AndroidVuplexWebView]"; + +#if UNITY_ANDROID && !UNITY_EDITOR && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE + private CanvasWebViewPrefab? _webViewPrefab; +#endif + private readonly Dictionary> _jsHandlers = new Dictionary>(); + private readonly RawImage _canvasReference; + private bool _isInitialized = false; + + public event Action? OnJavaScriptMessage; + public event Action? OnLoadFinished; + public event Action? OnLoadStarted; + + // Safe access - check initialization +#if UNITY_ANDROID && !UNITY_EDITOR && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE + public bool IsVisible => _webViewPrefab?.Visible ?? false; + public string CurrentUrl => _webViewPrefab?.WebView?.Url ?? ""; +#else + public bool IsVisible => false; + public string CurrentUrl => ""; +#endif + + public AndroidVuplexWebView(RawImage canvasReference) + { + _canvasReference = canvasReference ?? throw new ArgumentNullException(nameof(canvasReference)); + } + + public void Initialize(PassportWebViewConfig config) + { + if (_isInitialized) + { + PassportLogger.Warn($"{TAG} Already initialized, skipping"); + return; + } + +#if UNITY_ANDROID && !UNITY_EDITOR && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE + try + { + PassportLogger.Info($"{TAG} Initializing Vuplex WebView..."); + + // Start async initialization but don't wait + InitializeAsync(config).Forget(); + } + catch (Exception ex) + { + PassportLogger.Error($"{TAG} Failed to initialize: {ex.Message}"); + throw; + } +#else + PassportLogger.Warn($"{TAG} Vuplex WebView is only supported on Android builds, not in editor"); + _isInitialized = true; +#endif + } + +#if UNITY_ANDROID && !UNITY_EDITOR && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE + private async UniTaskVoid InitializeAsync(PassportWebViewConfig config) + { + try + { + // Create WebView prefab and parent to Canvas + _webViewPrefab = CanvasWebViewPrefab.Instantiate(); + _webViewPrefab.Native2DModeEnabled = true; + + // Must be child of Canvas for Vuplex to work + _webViewPrefab.transform.SetParent(_canvasReference.canvas.transform, false); + + // Set up full-screen layout for Native 2D Mode + var rect = _webViewPrefab.GetComponent(); + rect.anchorMin = Vector2.zero; + rect.anchorMax = Vector2.one; + rect.offsetMin = rect.offsetMax = Vector2.zero; + + // Wait for WebView initialization + await _webViewPrefab.WaitUntilInitialized(); + + // Setup event handlers + _webViewPrefab.WebView.LoadProgressChanged += (s, e) => + { + if (e.Type == ProgressChangeType.Started) + { + OnLoadStarted?.Invoke(); + } + else if (e.Type == ProgressChangeType.Finished) + { + OnLoadFinished?.Invoke(); + } + }; + _webViewPrefab.WebView.MessageEmitted += (s, e) => + { + foreach (var h in _jsHandlers) + { + if (e.Value.StartsWith($"{h.Key}:")) + { + h.Value?.Invoke(e.Value.Substring(h.Key.Length + 1)); + return; + } + } + + OnJavaScriptMessage?.Invoke(e.Value); + }; + _webViewPrefab.WebView.LoadFailed += (s, e) => PassportLogger.Warn($"{TAG} Load failed: {e.NativeErrorCode} for {e.Url}"); + + _isInitialized = true; + PassportLogger.Info($"{TAG} Vuplex WebView initialized successfully"); + } + catch (Exception ex) + { + PassportLogger.Error($"{TAG} Failed to initialize async: {ex.Message}"); + throw; + } + } +#endif + + public void LoadUrl(string url) + { +#if UNITY_ANDROID && !UNITY_EDITOR && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE + if (!_isInitialized || _webViewPrefab?.WebView == null) + { + PassportLogger.Error($"{TAG} Cannot load URL - WebView not initialized"); + return; + } + + _webViewPrefab.WebView.LoadUrl(url); +#else + PassportLogger.Warn($"{TAG} LoadUrl not supported in editor mode"); +#endif + } + + public void Show() + { +#if UNITY_ANDROID && !UNITY_EDITOR && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE + if (_webViewPrefab != null) + { + _webViewPrefab.Visible = true; + } +#endif + } + + public void Hide() + { +#if UNITY_ANDROID && !UNITY_EDITOR && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE + if (_webViewPrefab != null) + { + _webViewPrefab.Visible = false; + } +#endif + } + + public void ExecuteJavaScript(string js) + { +#if UNITY_ANDROID && !UNITY_EDITOR && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE + if (!_isInitialized || _webViewPrefab?.WebView == null) + { + PassportLogger.Error($"{TAG} Cannot execute JavaScript - WebView not initialized"); + return; + } + + _webViewPrefab.WebView.ExecuteJavaScript(js); +#endif + } + + public void RegisterJavaScriptMethod(string methodName, Action handler) + { + _jsHandlers[methodName] = handler; + +#if UNITY_ANDROID && !UNITY_EDITOR && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE + if (_isInitialized && _webViewPrefab?.WebView != null) + { + ExecuteJavaScript($"window.{methodName}=d=>window.vuplex?.postMessage('{methodName}:'+(typeof d==='object'?JSON.stringify(d):d))"); + } +#endif + } + + public void Dispose() + { +#if UNITY_ANDROID && !UNITY_EDITOR && VUPLEX_AVAILABLE && VUPLEX_AVAILABLE + if (_webViewPrefab != null) + { + _webViewPrefab.Destroy(); + _webViewPrefab = null; + } +#endif + + _jsHandlers.Clear(); + _isInitialized = false; + } + } +} diff --git a/src/Packages/Passport/Runtime/Scripts/Private/UI/AndroidVuplexWebView.cs.meta b/src/Packages/Passport/Runtime/Scripts/Private/UI/AndroidVuplexWebView.cs.meta new file mode 100644 index 000000000..e22e27500 --- /dev/null +++ b/src/Packages/Passport/Runtime/Scripts/Private/UI/AndroidVuplexWebView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ee900f6b1f028034f8399f83385c3a44 \ No newline at end of file diff --git a/src/Packages/Passport/Runtime/Scripts/Private/UI/WindowsPassportWebView.cs b/src/Packages/Passport/Runtime/Scripts/Private/UI/WindowsPassportWebView.cs index da60cf90e..d8afe9b90 100644 --- a/src/Packages/Passport/Runtime/Scripts/Private/UI/WindowsPassportWebView.cs +++ b/src/Packages/Passport/Runtime/Scripts/Private/UI/WindowsPassportWebView.cs @@ -355,7 +355,7 @@ public void Dispose() } } - #region Private Implementation + #region Private Implementation private void CreateUWBInstance() { @@ -689,7 +689,7 @@ private bool IsPortAvailable(int port) } } - #endregion + #endregion } #endif } diff --git a/src/Packages/Passport/Runtime/Scripts/Private/UI/iOSPassportWebView.cs b/src/Packages/Passport/Runtime/Scripts/Private/UI/iOSPassportWebView.cs index 3c7320792..64daa8ef3 100644 --- a/src/Packages/Passport/Runtime/Scripts/Private/UI/iOSPassportWebView.cs +++ b/src/Packages/Passport/Runtime/Scripts/Private/UI/iOSPassportWebView.cs @@ -3,13 +3,13 @@ using UnityEngine.UI; using Immutable.Passport.Core.Logging; -#if UNITY_IOS && !UNITY_EDITOR +#if UNITY_IOS || UNITY_EDITOR_OSX using Immutable.Browser.Gree; #endif namespace Immutable.Passport { -#if UNITY_IOS && !UNITY_EDITOR +#if UNITY_IOS || UNITY_EDITOR_OSX /// /// iOS implementation of IPassportWebView using Gree WebView (WKWebView) /// Wraps Gree WebViewObject in a clean, platform-agnostic interface @@ -236,7 +236,7 @@ public void Dispose() } } - #region Private Implementation + #region Private Implementation private void CreateWebViewObject() { @@ -246,8 +246,7 @@ private void CreateWebViewObject() webViewGameObject = new GameObject("PassportUI_iOS_WebView"); UnityEngine.Object.DontDestroyOnLoad(webViewGameObject); - // Add WebViewObject component - webViewObject = webViewGameObject.AddComponent(); + webViewObject = new WebViewObject(); PassportLogger.Info($"{TAG} Gree WebViewObject created successfully"); } @@ -266,8 +265,6 @@ private void ConfigureWebView() ); // Configure WebView settings - webViewObject.SetMargins(0, 0, 0, 0); // Full screen - webViewObject.SetVisibility(false); // Start hidden // Clear cache if requested if (config.ClearCacheOnInit) @@ -279,9 +276,9 @@ private void ConfigureWebView() PassportLogger.Info($"{TAG} Gree WebView configured successfully"); } - #endregion + #endregion - #region WebView Event Handlers + #region WebView Event Handlers private void OnWebViewMessage(string message) { @@ -329,7 +326,7 @@ private void OnWebViewLog(string message) PassportLogger.Debug($"{TAG} WebView console log: {message}"); } - #endregion + #endregion } #endif } diff --git a/src/Packages/Passport/Runtime/Scripts/Public/Passport.cs b/src/Packages/Passport/Runtime/Scripts/Public/Passport.cs index 83fc4e270..0d667317b 100644 --- a/src/Packages/Passport/Runtime/Scripts/Public/Passport.cs +++ b/src/Packages/Passport/Runtime/Scripts/Public/Passport.cs @@ -237,6 +237,7 @@ private async UniTask Initialise( #elif (UNITY_ANDROID && !UNITY_EDITOR_WIN) || (UNITY_IPHONE && !UNITY_EDITOR_WIN) || UNITY_STANDALONE_OSX || UNITY_WEBGL // Initialise default browser client for Android, iOS, and macOS _webBrowserClient = new GreeBrowserClient(); + await UniTask.CompletedTask; #else throw new PassportException("Platform not supported"); #endif diff --git a/src/Packages/Passport/Runtime/Scripts/Public/PassportUI.cs b/src/Packages/Passport/Runtime/Scripts/Public/PassportUI.cs index 349b1eb37..f2b2f033e 100644 --- a/src/Packages/Passport/Runtime/Scripts/Public/PassportUI.cs +++ b/src/Packages/Passport/Runtime/Scripts/Public/PassportUI.cs @@ -83,7 +83,6 @@ public class PassportUI : MonoBehaviour private RawImage rawImage; // Login completion source removed - OAuth handled by external browser private bool isInitialized = false; - private bool pointerEnterTriggered = false; private GameObject bridgeWebViewGameObject; // Input management @@ -202,7 +201,7 @@ private IPassportWebView CreatePlatformWebView() return new iOSPassportWebView(rawImage, this); #elif UNITY_ANDROID PassportLogger.Info($"{TAG} Creating Android WebView"); - return new AndroidPassportWebView(rawImage, this); + return new AndroidVuplexWebView(rawImage); #elif UNITY_STANDALONE_OSX PassportLogger.Info($"{TAG} Creating macOS WebView (WKWebView)"); // TODO: Implement macOS WebView @@ -459,8 +458,6 @@ public void HideLoginUI(bool logMessage = true) bridgeWebViewGameObject = null; // Clear reference } - // Reset pointer enter flag for next login - pointerEnterTriggered = false; } ///