package cc.unitmesh.viewer.web.webedit import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json /** * Android implementation of WebEditBridge using WebViewNavigator */ class AndroidWebEditBridge : WebEditBridge { private val _currentUrl = MutableStateFlow("") override val currentUrl: StateFlow = _currentUrl.asStateFlow() private val _pageTitle = MutableStateFlow("") override val pageTitle: StateFlow = _pageTitle.asStateFlow() private val _isLoading = MutableStateFlow(false) override val isLoading: StateFlow = _isLoading.asStateFlow() private val _loadProgress = MutableStateFlow(0) override val loadProgress: StateFlow = _loadProgress.asStateFlow() private val _domTree = MutableStateFlow(null) override val domTree: StateFlow = _domTree.asStateFlow() private val _selectedElement = MutableStateFlow(null) override val selectedElement: StateFlow = _selectedElement.asStateFlow() private val _d2SnapTree = MutableStateFlow(null) override val d2SnapTree: StateFlow = _d2SnapTree.asStateFlow() private val _accessibilityTree = MutableStateFlow(null) override val accessibilityTree: StateFlow = _accessibilityTree.asStateFlow() private val _actionableElements = MutableStateFlow>(emptyList()) override val actionableElements: StateFlow> = _actionableElements.asStateFlow() private val _lastActionResult = MutableStateFlow(null) override val lastActionResult: StateFlow = _lastActionResult.asStateFlow() private val _lastScreenshot = MutableStateFlow(null) override val lastScreenshot: StateFlow = _lastScreenshot.asStateFlow() private val _isSelectionMode = MutableStateFlow(false) override val isSelectionMode: StateFlow = _isSelectionMode.asStateFlow() private val _isReady = MutableStateFlow(false) override val isReady: StateFlow = _isReady.asStateFlow() private val _errorMessage = MutableStateFlow(null) override val errorMessage: StateFlow = _errorMessage.asStateFlow() private val json = Json { encodeDefaults = true; ignoreUnknownKeys = true } var executeJavaScript: ((String) -> Unit)? = null var navigateCallback: ((String) -> Unit)? = null var reloadCallback: (() -> Unit)? = null var goBackCallback: (() -> Unit)? = null var goForwardCallback: (() -> Unit)? = null override suspend fun navigateTo(url: String) { println("[AndroidWebEditBridge] 🚀 navigateTo called: '$url'") _isLoading.value = true _currentUrl.value = url _errorMessage.value = null try { navigateCallback?.invoke(url) } catch (e: Exception) { _errorMessage.value = "Failed to navigate: ${e.message}" _isLoading.value = false } } override suspend fun reload() { _isLoading.value = true reloadCallback?.invoke() } override suspend fun goBack() { goBackCallback?.invoke() } override suspend fun goForward() { goForwardCallback?.invoke() } override suspend fun setSelectionMode(enabled: Boolean) { _isSelectionMode.value = enabled executeJavaScript?.invoke("window.webEditBridge?.setSelectionMode($enabled);") } override suspend fun enableInspectMode() { _isSelectionMode.value = true executeJavaScript?.invoke("window.webEditBridge?.enableInspectMode();") } override suspend fun disableInspectMode() { _isSelectionMode.value = false executeJavaScript?.invoke("window.webEditBridge?.disableInspectMode();") } override suspend fun highlightElement(selector: String) { val escapedSelector = selector .replace("\\", "\\\\") .replace("'", "\\'") .replace("\"", "\\\"") executeJavaScript?.invoke("window.webEditBridge?.highlightElement('$escapedSelector');") } override suspend fun clearHighlights() { executeJavaScript?.invoke("window.webEditBridge?.clearHighlights();") } override suspend fun scrollToElement(selector: String) { val escapedSelector = selector .replace("\\", "\\\\") .replace("'", "\\'") .replace("\"", "\\\"") executeJavaScript?.invoke("window.webEditBridge?.scrollToElement('$escapedSelector');") } override suspend fun refreshDOMTree() { executeJavaScript?.invoke("window.webEditBridge?.getDOMTree();") } override suspend fun refreshD2SnapTree() { executeJavaScript?.invoke("window.webEditBridge?.getD2SnapTree();") } override suspend fun refreshAccessibilityTree() { executeJavaScript?.invoke("window.webEditBridge?.getAccessibilityTree();") } override suspend fun refreshActionableElements() { executeJavaScript?.invoke("window.webEditBridge?.getActionableElements();") } override suspend fun getElementAtPoint(x: Int, y: Int): DOMElement? { throw NotImplementedError("getElementAtPoint requires callback mechanism") } override suspend fun getSelectedElementHtml(): String? { return null } override suspend fun performAction(action: WebEditAction) { val payload = json.encodeToString(action) executeJavaScript?.invoke("window.webEditBridge?.performAction($payload);") } override suspend fun click(selector: String) { performAction(WebEditAction(action = "click", selector = selector)) } override suspend fun typeText(selector: String, text: String, clearFirst: Boolean) { performAction(WebEditAction(action = "type", selector = selector, text = text, clearFirst = clearFirst)) } override suspend fun selectOption(selector: String, value: String) { performAction(WebEditAction(action = "select", selector = selector, value = value)) } override suspend fun pressKey(key: String, selector: String?) { performAction(WebEditAction(action = "pressKey", selector = selector, key = key)) } override suspend fun captureScreenshot(maxWidth: Int, quality: Double) { executeJavaScript?.invoke("window.webEditBridge?.captureScreenshot({ maxWidth: $maxWidth, quality: $quality });") } override fun markReady() { _isReady.value = true } override fun handleMessage(message: WebEditMessage) { when (message) { is WebEditMessage.DOMTreeUpdated -> _domTree.value = message.root is WebEditMessage.D2SnapTreeUpdated -> _d2SnapTree.value = message.root is WebEditMessage.AccessibilityTreeUpdated -> _accessibilityTree.value = message.root is WebEditMessage.ActionableElementsUpdated -> _actionableElements.value = message.elements is WebEditMessage.ElementSelected -> _selectedElement.value = message.element is WebEditMessage.PageLoaded -> { _currentUrl.value = message.url _pageTitle.value = message.title _isLoading.value = false _loadProgress.value = 100 } is WebEditMessage.Error -> _errorMessage.value = message.message is WebEditMessage.LoadProgress -> _loadProgress.value = message.progress is WebEditMessage.ActionResult -> { _lastActionResult.value = message if (!message.ok) _errorMessage.value = message.message ?: "Action failed: ${message.action}" } is WebEditMessage.ScreenshotCaptured -> { _lastScreenshot.value = message if (message.error != null) _errorMessage.value = message.error } else -> {} } } fun setUrl(url: String) { _currentUrl.value = url } fun setTitle(title: String) { _pageTitle.value = title } fun setLoading(loading: Boolean) { _isLoading.value = loading } } actual fun createWebEditBridge(): WebEditBridge { return AndroidWebEditBridge() }