using Android.Content; using Android.Webkit; using Microsoft.Maui.Controls; using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; using System; using System.Diagnostics; using System.IO; using System.Net; using System.Threading.Tasks; using Xamarin.Forms.Clinical6.UI.Controls; namespace MAUI.Clinical6.Platforms.Android.Renders { public class PdfWebViewerHandler : ViewHandler { private static readonly WebClient _webClient = new WebClient(); private WebViewClientDelegate _webDelegate; public static IPropertyMapper Mapper = new PropertyMapper(ViewHandler.ViewMapper) { [nameof(PdfWebViewer.Uri)] = MapUri }; public PdfWebViewerHandler() : base(Mapper) { } protected override global::Android.Webkit.WebView CreatePlatformView() { var webView = new global::Android.Webkit.WebView(Context); // Enhanced settings for MAUI compatibility webView.Settings.JavaScriptEnabled = true; webView.Settings.AllowFileAccess = true; webView.Settings.AllowContentAccess = true; webView.Settings.AllowUniversalAccessFromFileURLs = true; webView.Settings.AllowFileAccessFromFileURLs = true; webView.Settings.DomStorageEnabled = true; webView.Settings.MixedContentMode = MixedContentHandling.AlwaysAllow; // Add WebChromeClient for JavaScript console logging webView.SetWebChromeClient(new DebugWebChromeClient()); return webView; } protected override void ConnectHandler(global::Android.Webkit.WebView platformView) { base.ConnectHandler(platformView); if (VirtualView != null && platformView != null) { _webDelegate = new WebViewClientDelegate(VirtualView); platformView.SetWebViewClient(_webDelegate); if (!string.IsNullOrEmpty(VirtualView.Uri)) { CheckLocalDownload(GetFileNameFromUri()); } } } private static void MapUri(PdfWebViewerHandler handler, PdfWebViewer view) { handler?.CheckLocalDownload(handler.GetFileNameFromUri()); } private string GetFileNameFromUri() { if (VirtualView != null && !string.IsNullOrEmpty(VirtualView.Uri)) { var uri = new Uri(VirtualView.Uri); return Path.GetFileName(uri.AbsolutePath); } return $"{Guid.NewGuid()}.pdf"; } private async void CheckLocalDownload(string fileName) { try { var fileDownloaded = await DownloadFile(VirtualView.Uri, fileName); if (fileDownloaded) { await LoadUrl(fileName); } } catch (Exception ex) { Console.WriteLine($"CheckLocalDownload Exception: {ex}"); } } private async Task DownloadFile(string uri, string filename) { try { string documentsPath = Path.Combine(FileSystem.AppDataDirectory, "Documents"); if (!Directory.Exists(documentsPath)) Directory.CreateDirectory(documentsPath); string localPath = Path.Combine(documentsPath, filename); // Check if file already exists if (File.Exists(localPath)) { var fileInfo = new FileInfo(localPath); if (fileInfo.Length > 0) return true; } // Use HttpClient instead of WebClient using var httpClient = new HttpClient(); httpClient.Timeout = TimeSpan.FromMinutes(5); var response = await httpClient.GetAsync(uri); if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsByteArrayAsync(); await File.WriteAllBytesAsync(localPath, content); return File.Exists(localPath); } return false; } catch (Exception exc) { System.Diagnostics.Debug.WriteLine($"Download failed: {exc.Message}"); return false; } } private async Task LoadUrl(string fileName) { string documentsPath = Path.Combine(FileSystem.AppDataDirectory, "Documents"); string localPath = Path.Combine(documentsPath, fileName); if (!File.Exists(localPath)) { await ShowErrorMessage("File not found", "The requested file could not be located."); return; } string fileType = Path.GetExtension(localPath).Replace(".", string.Empty).ToLower(); if (PlatformView == null || VirtualView == null) return; try { switch (fileType) { case "pdf": // Use PDF.js viewer if (await CheckAssetExists("pdfjs/web/viewer.html")) { await LoadPdfFile(localPath, fileName); } else { await ShowErrorMessage("PDF Viewer Not Available", "PDF.js is not installed."); } break; case "jpg": case "jpeg": case "png": case "gif": case "bmp": case "webp": await LoadImageFile(localPath); break; case "txt": //await LoadTextFile(localPath); // keep existing text loader if you have one break; default: await ShowErrorMessage("Unsupported File Type", $"Cannot display . {fileType} files in this viewer."); break; } } catch (Exception ex) { await ShowErrorMessage("Loading Error", $"An error occurred while loading the file: {ex.Message}"); } } private string CreateCustomPdfViewer(string pdfPath, string fileName) { string html = $@" {fileName}
Loading PDF...
"; return html; } private async Task CheckAssetExists(string assetPath) { try { using var stream = Context.Assets.Open(assetPath); return stream != null; } catch { return false; } } private async Task LoadPdfFile(string localPath, string fileName) { System.Diagnostics.Debug.WriteLine($"📄 LoadPdfFile called for: {fileName}"); System.Diagnostics.Debug.WriteLine($"📁 Local path: {localPath}"); try { // Copy PDF to a location accessible to PDF.js string cacheDir = Context.CacheDir.AbsolutePath; string pdfCacheDir = Path.Combine(cacheDir, "pdfs"); if (!Directory.Exists(pdfCacheDir)) { Directory.CreateDirectory(pdfCacheDir); System.Diagnostics.Debug.WriteLine($"✅ Created cache directory: {pdfCacheDir}"); } string cachedPdfPath = Path.Combine(pdfCacheDir, fileName); File.Copy(localPath, cachedPdfPath, true); System.Diagnostics.Debug.WriteLine($"✅ PDF copied to cache: {cachedPdfPath}"); System.Diagnostics.Debug.WriteLine($"📊 File size: {new FileInfo(cachedPdfPath).Length} bytes"); // Create custom PDF viewer HTML string customHtml = CreateCustomPdfViewer(cachedPdfPath, fileName); System.Diagnostics.Debug.WriteLine($"Loading custom PDF.js viewer"); PlatformView.LoadDataWithBaseURL("file:///android_asset/", customHtml, "text/html", "UTF-8", null); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"PDF.js failed: {ex.Message}"); System.Diagnostics.Debug.WriteLine($"Stack trace: {ex.StackTrace}"); await ShowErrorMessage("PDF Error", $"Failed to load PDF: {ex.Message}"); } } private async Task LoadImageFile(string imagePath) { try { byte[] imageBytes = await File.ReadAllBytesAsync(imagePath); string base64 = Convert.ToBase64String(imageBytes); string extension = Path.GetExtension(imagePath).ToLower(); string mimeType = extension switch { ".jpg" or ".jpeg" => "image/jpeg", ".png" => "image/png", ".gif" => "image/gif", ".bmp" => "image/bmp", ".webp" => "image/webp", _ => "image/jpeg" }; string html = $@" Image "; PlatformView.LoadDataWithBaseURL("file:///android_asset/", html, "text/html", "UTF-8", null); } catch (Exception ex) { await ShowErrorMessage("Image Error", $"Failed to load image file: {ex.Message}"); } } private async Task ShowErrorMessage(string title, string message) { string html = $@"

{title}

{message}

"; PlatformView.LoadDataWithBaseURL(null, html, "text/html", "UTF-8", null); } private class WebViewClientDelegate : WebViewClient { private readonly PdfWebViewer _pdfWebViewer; public WebViewClientDelegate(PdfWebViewer pdfWebViewer) { _pdfWebViewer = pdfWebViewer; } public override void OnPageStarted(global::Android.Webkit.WebView view, string url, global::Android.Graphics.Bitmap favicon) { base.OnPageStarted(view, url, favicon); System.Diagnostics.Debug.WriteLine($"🔄 Page started: {url}"); } public override void OnPageFinished(global::Android.Webkit.WebView view, string url) { base.OnPageFinished(view, url); System.Diagnostics.Debug.WriteLine($"Page finished: {url}"); // Check if PDF.js viewer loaded if (url.Contains("pdfjs/web/viewer.html")) { // Inject JavaScript to check PDF.js status string checkScript = @" setTimeout(function() { if (window.PDFViewerApplication) { console.log('PDFViewerApplication exists'); console.log('PDF loaded:', window.PDFViewerApplication.pdfDocument !== null); if (window.PDFViewerApplication.pdfDocument) { console.log('Number of pages:', window.PDFViewerApplication.pdfDocument.numPages); } console.log('Viewer initialized:', window.PDFViewerApplication.initialized); } else { console.log('PDFViewerApplication not found'); } }, 2000); "; view.EvaluateJavascript(checkScript, null); System.Diagnostics.Debug.WriteLine($"Injected PDF.js status check"); } _pdfWebViewer?.OnUrlLoaded(); } public override void OnReceivedError(global::Android.Webkit.WebView view, IWebResourceRequest request, WebResourceError error) { base.OnReceivedError(view, request, error); System.Diagnostics.Debug.WriteLine($"WebView Error: {error.Description} for URL: {request.Url}"); } } private class DebugWebChromeClient : WebChromeClient { public override bool OnConsoleMessage(ConsoleMessage consoleMessage) { System.Diagnostics.Debug.WriteLine($"📋 JS Console [{consoleMessage.InvokeMessageLevel()}]: {consoleMessage.Message()} at {consoleMessage.SourceId()}:{consoleMessage.LineNumber()}"); return base.OnConsoleMessage(consoleMessage); } } } }