Skip to content

Commit 00bb772

Browse files
committed
feat: add android webview implementation
1 parent f70b6c2 commit 00bb772

File tree

2 files changed

+344
-0
lines changed

2 files changed

+344
-0
lines changed
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
using System;
2+
using UnityEngine;
3+
using UnityEngine.UI;
4+
using Immutable.Passport.Core.Logging;
5+
6+
#if UNITY_ANDROID && !UNITY_EDITOR
7+
using Immutable.Browser.Gree;
8+
#endif
9+
10+
namespace Immutable.Passport
11+
{
12+
#if UNITY_ANDROID && !UNITY_EDITOR
13+
/// <summary>
14+
/// Android implementation of IPassportWebView using Gree WebView (Android WebView)
15+
/// Wraps Gree WebViewObject in a clean, platform-agnostic interface
16+
/// Very similar to iOS implementation but uses Android's native WebView
17+
/// </summary>
18+
public class AndroidPassportWebView : IPassportWebView
19+
{
20+
private const string TAG = "[AndroidPassportWebView]";
21+
22+
// Gree WebView components
23+
private WebViewObject webViewObject;
24+
private GameObject webViewGameObject;
25+
26+
// Configuration and state
27+
private RawImage targetRawImage;
28+
private MonoBehaviour coroutineRunner;
29+
private PassportWebViewConfig config;
30+
private bool isInitialized = false;
31+
private bool isVisible = false;
32+
private string currentUrl = "";
33+
34+
// Events
35+
public event Action<string> OnJavaScriptMessage;
36+
public event Action OnLoadFinished;
37+
public event Action OnLoadStarted;
38+
39+
// Properties
40+
public bool IsVisible => isVisible;
41+
public string CurrentUrl => currentUrl;
42+
43+
/// <summary>
44+
/// Constructor for Android PassportWebView
45+
/// </summary>
46+
/// <param name="targetRawImage">RawImage component to display WebView content (not used on Android - native overlay)</param>
47+
/// <param name="coroutineRunner">MonoBehaviour to run coroutines (for consistency with other platforms)</param>
48+
public AndroidPassportWebView(RawImage targetRawImage, MonoBehaviour coroutineRunner)
49+
{
50+
this.targetRawImage = targetRawImage ?? throw new ArgumentNullException(nameof(targetRawImage));
51+
this.coroutineRunner = coroutineRunner ?? throw new ArgumentNullException(nameof(coroutineRunner));
52+
53+
PassportLogger.Info($"{TAG} Android WebView wrapper created");
54+
}
55+
56+
public void Initialize(PassportWebViewConfig config)
57+
{
58+
if (isInitialized)
59+
{
60+
PassportLogger.Warn($"{TAG} Already initialized, skipping");
61+
return;
62+
}
63+
64+
this.config = config ?? new PassportWebViewConfig();
65+
66+
try
67+
{
68+
PassportLogger.Info($"{TAG} Initializing Android WebView with Gree WebView...");
69+
70+
CreateWebViewObject();
71+
ConfigureWebView();
72+
73+
isInitialized = true;
74+
PassportLogger.Info($"{TAG} Android WebView initialized successfully");
75+
}
76+
catch (Exception ex)
77+
{
78+
PassportLogger.Error($"{TAG} Failed to initialize: {ex.Message}");
79+
throw;
80+
}
81+
}
82+
83+
public void LoadUrl(string url)
84+
{
85+
if (!isInitialized)
86+
{
87+
PassportLogger.Error($"{TAG} Cannot load URL - WebView not initialized");
88+
return;
89+
}
90+
91+
if (string.IsNullOrEmpty(url))
92+
{
93+
PassportLogger.Error($"{TAG} Cannot load empty URL");
94+
return;
95+
}
96+
97+
try
98+
{
99+
PassportLogger.Info($"{TAG} Loading URL: {url}");
100+
currentUrl = url;
101+
102+
// Android implementation: Use LaunchAuthURL to open in external browser
103+
// This follows the same pattern as iOS and the main Passport SDK for Android authentication
104+
webViewObject.LaunchAuthURL(url, "immutablerunner://callback");
105+
PassportLogger.Info($"{TAG} URL launched in external browser: {url}");
106+
107+
// Trigger load started event
108+
OnLoadStarted?.Invoke();
109+
110+
// For Android, we consider the load "finished" immediately since it opens externally
111+
OnLoadFinished?.Invoke();
112+
}
113+
catch (Exception ex)
114+
{
115+
PassportLogger.Error($"{TAG} Failed to load URL: {ex.Message}");
116+
}
117+
}
118+
119+
public void Show()
120+
{
121+
if (!isInitialized)
122+
{
123+
PassportLogger.Error($"{TAG} Cannot show - WebView not initialized");
124+
return;
125+
}
126+
127+
try
128+
{
129+
PassportLogger.Info($"{TAG} Showing WebView (Android uses external browser)");
130+
131+
// Android implementation: WebView is always "shown" since we use external browser
132+
// The actual display happens when LoadUrl calls LaunchAuthURL
133+
isVisible = true;
134+
135+
PassportLogger.Info($"{TAG} WebView shown successfully");
136+
}
137+
catch (Exception ex)
138+
{
139+
PassportLogger.Error($"{TAG} Failed to show WebView: {ex.Message}");
140+
}
141+
}
142+
143+
public void Hide()
144+
{
145+
if (!isInitialized)
146+
{
147+
PassportLogger.Warn($"{TAG} Cannot hide - WebView not initialized");
148+
return;
149+
}
150+
151+
try
152+
{
153+
PassportLogger.Info($"{TAG} Hiding WebView (Android external browser will close automatically)");
154+
155+
// Android implementation: External browser closes automatically after auth
156+
// No explicit hide needed
157+
isVisible = false;
158+
159+
PassportLogger.Info($"{TAG} WebView hidden successfully");
160+
}
161+
catch (Exception ex)
162+
{
163+
PassportLogger.Error($"{TAG} Failed to hide WebView: {ex.Message}");
164+
}
165+
}
166+
167+
public void ExecuteJavaScript(string js)
168+
{
169+
if (!isInitialized)
170+
{
171+
PassportLogger.Error($"{TAG} Cannot execute JavaScript - WebView not initialized");
172+
return;
173+
}
174+
175+
try
176+
{
177+
PassportLogger.Debug($"{TAG} Executing JavaScript: {js.Substring(0, Math.Min(100, js.Length))}...");
178+
webViewObject.EvaluateJS(js);
179+
}
180+
catch (Exception ex)
181+
{
182+
PassportLogger.Error($"{TAG} Failed to execute JavaScript: {ex.Message}");
183+
}
184+
}
185+
186+
public void RegisterJavaScriptMethod(string methodName, Action<string> handler)
187+
{
188+
if (!isInitialized)
189+
{
190+
PassportLogger.Error($"{TAG} Cannot register JavaScript method - WebView not initialized");
191+
return;
192+
}
193+
194+
try
195+
{
196+
PassportLogger.Info($"{TAG} Registering JavaScript method: {methodName}");
197+
198+
// Android implementation: Since we use external browser (Chrome Custom Tabs),
199+
// JavaScript methods are handled through deep links and auth callbacks
200+
// The login data will be captured via the auth callback when the external browser
201+
// redirects back to the app with immutablerunner://callback
202+
203+
PassportLogger.Info($"{TAG} JavaScript method '{methodName}' registered (handled via deep link callbacks on Android)");
204+
}
205+
catch (Exception ex)
206+
{
207+
PassportLogger.Error($"{TAG} Failed to register JavaScript method: {ex.Message}");
208+
}
209+
}
210+
211+
public void Dispose()
212+
{
213+
try
214+
{
215+
PassportLogger.Info($"{TAG} Disposing Android WebView");
216+
217+
// Destroy WebView GameObject
218+
if (webViewGameObject != null)
219+
{
220+
UnityEngine.Object.DestroyImmediate(webViewGameObject);
221+
webViewGameObject = null;
222+
}
223+
224+
// Clear references
225+
webViewObject = null;
226+
targetRawImage = null;
227+
coroutineRunner = null;
228+
229+
isInitialized = false;
230+
isVisible = false;
231+
232+
PassportLogger.Info($"{TAG} Android WebView disposed successfully");
233+
}
234+
catch (Exception ex)
235+
{
236+
PassportLogger.Error($"{TAG} Error during disposal: {ex.Message}");
237+
}
238+
}
239+
240+
#region Private Implementation
241+
242+
private void CreateWebViewObject()
243+
{
244+
PassportLogger.Info($"{TAG} Creating Gree WebViewObject...");
245+
246+
// Create GameObject for WebView
247+
webViewGameObject = new GameObject("PassportUI_Android_WebView");
248+
UnityEngine.Object.DontDestroyOnLoad(webViewGameObject);
249+
250+
// Add WebViewObject component
251+
webViewObject = webViewGameObject.AddComponent<WebViewObject>();
252+
253+
PassportLogger.Info($"{TAG} Gree WebViewObject created successfully");
254+
}
255+
256+
private void ConfigureWebView()
257+
{
258+
PassportLogger.Info($"{TAG} Configuring Gree WebView...");
259+
260+
// Initialize WebViewObject with callbacks
261+
webViewObject.Init(
262+
cb: OnWebViewMessage,
263+
httpErr: OnWebViewHttpError,
264+
err: OnWebViewError,
265+
auth: OnWebViewAuth,
266+
log: OnWebViewLog
267+
);
268+
269+
// Clear cache if requested
270+
if (config.ClearCacheOnInit)
271+
{
272+
webViewObject.ClearCache(true);
273+
PassportLogger.Info($"{TAG} WebView cache cleared");
274+
}
275+
276+
PassportLogger.Info($"{TAG} Gree WebView configured successfully");
277+
}
278+
279+
#endregion
280+
281+
#region WebView Event Handlers
282+
283+
private void OnWebViewMessage(string message)
284+
{
285+
try
286+
{
287+
PassportLogger.Debug($"{TAG} WebView message: {message}");
288+
289+
// Parse message to see if it's a JavaScript method call
290+
if (message.Contains("method") && message.Contains("data"))
291+
{
292+
// This is a JavaScript method call from our registered methods
293+
OnJavaScriptMessage?.Invoke(message);
294+
}
295+
else
296+
{
297+
// Regular WebView message
298+
OnJavaScriptMessage?.Invoke(message);
299+
}
300+
}
301+
catch (Exception ex)
302+
{
303+
PassportLogger.Error($"{TAG} Error handling WebView message: {ex.Message}");
304+
}
305+
}
306+
307+
private void OnWebViewHttpError(string id, string message)
308+
{
309+
PassportLogger.Error($"{TAG} WebView HTTP error [{id}]: {message}");
310+
}
311+
312+
private void OnWebViewError(string id, string message)
313+
{
314+
PassportLogger.Error($"{TAG} WebView error [{id}]: {message}");
315+
}
316+
317+
private void OnWebViewAuth(string message)
318+
{
319+
PassportLogger.Info($"{TAG} WebView auth message: {message}");
320+
// Auth messages could be login completion, etc.
321+
OnJavaScriptMessage?.Invoke(message);
322+
}
323+
324+
private void OnWebViewLog(string message)
325+
{
326+
PassportLogger.Debug($"{TAG} WebView console log: {message}");
327+
}
328+
329+
#endregion
330+
}
331+
#endif
332+
}
333+

src/Packages/Passport/Runtime/Scripts/Private/UI/AndroidPassportWebView.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)