compose-webview-multiplatform
compose-webview-multiplatform copied to clipboard
How to implement in wasmjs kotlin
ok I implemented this using some code.... I will give here code ... Please someone include in this library.....
package com.saralapps.common.handler.wasm
import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.remember import androidx.compose.runtime.snapshots.SnapshotStateObserver import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusManager import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusTarget import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.unit.round import kotlinx.browser.document import kotlinx.dom.createElement import org.w3c.dom.Document import org.w3c.dom.Element import kotlin.contracts.ExperimentalContracts
val NoOpUpdate: Element.() -> Unit = {}
private class ComponentInfo<T : Element> { lateinit var container: Element lateinit var component: T lateinit var updater: Updater<T> }
private class FocusSwitcher<T : Element>( private val info: ComponentInfo<T>, private val focusManager: FocusManager ) { private val backwardRequester = FocusRequester() private val forwardRequester = FocusRequester() private var isRequesting = false
fun moveBackward() {
try {
isRequesting = true
backwardRequester.requestFocus()
} finally {
isRequesting = false
}
focusManager.moveFocus(FocusDirection.Previous)
}
fun moveForward() {
try {
isRequesting = true
forwardRequester.requestFocus()
} finally {
isRequesting = false
}
focusManager.moveFocus(FocusDirection.Next)
}
@Composable
fun Content() {
Box(
Modifier
.focusRequester(backwardRequester)
.onFocusChanged {
if (it.isFocused && !isRequesting) {
focusManager.clearFocus(force = true)
val component = info.container.firstElementChild
if(component != null) {
requestFocus(component)
}else {
moveForward()
}
}
}
.focusTarget()
)
Box(
Modifier
.focusRequester(forwardRequester)
.onFocusChanged {
if (it.isFocused && !isRequesting) {
focusManager.clearFocus(force = true)
val component = info.container.lastElementChild
if(component != null) {
requestFocus(component)
}else {
moveBackward()
}
}
}
.focusTarget()
)
}
}
private fun requestFocus(element: Element) : Unit = js(""" { element.focus(); } """)
private fun initializingElement(element: Element) : Unit = js(""" { element.style.position = 'absolute'; element.style.margin = '0px'; } """)
private fun changeCoordinates(element: Element,width: Float,height: Float,x: Float,y: Float) : Unit = js(""" { element.style.width = width + 'px'; element.style.height = height + 'px'; element.style.left = x + 'px'; element.style.top = y + 'px'; } """)
@OptIn(ExperimentalContracts::class) @Composable fun <T : Element> HtmlView( factory: Document.() -> T, modifier: Modifier = Modifier, update: (T) -> Unit = NoOpUpdate ) {
val componentInfo = remember { ComponentInfo<T>() }
val root = LocalLayerContainer.current
val density = LocalDensity.current.density
val focusManager = LocalFocusManager.current
val focusSwitcher = remember { FocusSwitcher(componentInfo, focusManager) }
Box(
modifier = modifier.onGloballyPositioned { coordinates ->
val location = coordinates.positionInWindow().round()
val size = coordinates.size
changeCoordinates(componentInfo.component,size.width / density, size.height / density, location.x / density,location.y / density)
}
) {
focusSwitcher.Content()
}
DisposableEffect(factory) {
componentInfo.container = document.createElement("div",NoOpUpdate)
componentInfo.component = document.factory()
root.insertBefore(componentInfo.container,root.firstChild)
componentInfo.container.append(componentInfo.component)
componentInfo.updater = Updater(componentInfo.component, update)
initializingElement(componentInfo.component)
onDispose {
root.removeChild(componentInfo.container)
componentInfo.updater.dispose()
}
}
SideEffect {
componentInfo.updater.update = update
}
}
private class Updater<T : Element>( private val component: T, update: (T) -> Unit ) { private var isDisposed = false
private val snapshotObserver = SnapshotStateObserver { command ->
command()
}
private val scheduleUpdate = { _: T ->
if(isDisposed.not()) {
performUpdate()
}
}
var update: (T) -> Unit = update
set(value) {
if (field != value) {
field = value
performUpdate()
}
}
private fun performUpdate() {
snapshotObserver.observeReads(component, scheduleUpdate) {
update(component)
}
}
init {
snapshotObserver.start()
performUpdate()
}
fun dispose() {
snapshotObserver.stop()
snapshotObserver.clear()
isDisposed = true
}
}
import androidx.compose.runtime.staticCompositionLocalOf import org.w3c.dom.Element
val LocalLayerContainer = staticCompositionLocalOf<Element> { error("CompositionLocal LayerContainer not provided") // you can replace this with document.body!! }
this is HTML View that provides
HtmlView(
modifier = Modifier.fillMaxSize(),
factory = {
}
)
to render html code in wasm.......
and inside factoory i created iframe...
like below
factory = { val iFrame = createElement("iframe") val url = window.location.host iFrame.setAttribute("srcdoc", """
iFrame }
using this i rendered PDF in wasm as well using pdf.js "Javascript library".
only pdf portion is html and js and other is compose multiplatform
I checked this code @rohan-paudel and it is working, great job there. I created a container name it IWebView and use WebViewKMP for mobile and desktop and use your solution (HtmlView) for wasmjs
Here is my working code:
package app.ybee.ui.core.common.component
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateObserver
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusTarget
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.unit.round
import kotlinx.browser.document
import org.w3c.dom.Document
import org.w3c.dom.Element
import kotlin.contracts.ExperimentalContracts
val NoOpUpdate: Element.() -> Unit = {}
val LocalLayerContainer = staticCompositionLocalOf {
document.body!!
}
private class ComponentInfo<T : Element> {
lateinit var container: Element
lateinit var component: T
lateinit var updater: Updater<T>
}
private class FocusSwitcher<T : Element>(
private val info: ComponentInfo<T>,
private val focusManager: FocusManager
) {
private val backwardRequester = FocusRequester()
private val forwardRequester = FocusRequester()
private var isRequesting = false
fun moveBackward() {
try {
isRequesting = true
backwardRequester.requestFocus()
} finally {
isRequesting = false
}
focusManager.moveFocus(FocusDirection.Previous)
}
fun moveForward() {
try {
isRequesting = true
forwardRequester.requestFocus()
} finally {
isRequesting = false
}
focusManager.moveFocus(FocusDirection.Next)
}
@Composable
fun Content() {
Box(
Modifier
.focusRequester(backwardRequester)
.onFocusChanged {
if (it.isFocused && !isRequesting) {
focusManager.clearFocus(force = true)
val component = info.container.firstElementChild
if(component != null) {
requestFocus(component)
}else {
moveForward()
}
}
}
.focusTarget()
)
Box(
Modifier
.focusRequester(forwardRequester)
.onFocusChanged {
if (it.isFocused && !isRequesting) {
focusManager.clearFocus(force = true)
val component = info.container.lastElementChild
if(component != null) {
requestFocus(component)
}else {
moveBackward()
}
}
}
.focusTarget()
)
}
}
private fun requestFocus(element: Element) : Unit = js("""
{
element.focus();
}
""")
private fun initializingElement(element: Element) : Unit = js("""
{
element.style.position = 'absolute';
element.style.margin = '0px';
}
""")
private fun changeCoordinates(element: Element,width: Float,height: Float,x: Float,y: Float) : Unit = js("""
{
element.style.width = width + 'px';
element.style.height = height + 'px';
element.style.left = x + 'px';
element.style.top = y + 'px';
}
""")
@OptIn(ExperimentalContracts::class)
@Composable
fun <T : Element> HtmlView(
factory: Document.() -> T,
modifier: Modifier = Modifier,
update: (T) -> Unit = NoOpUpdate
) {
val componentInfo = remember { ComponentInfo<T>() }
val root = LocalLayerContainer.current
val density = LocalDensity.current.density
val focusManager = LocalFocusManager.current
val focusSwitcher = remember { FocusSwitcher(componentInfo, focusManager) }
Box(
modifier = modifier.onGloballyPositioned { coordinates ->
val location = coordinates.positionInWindow().round()
val size = coordinates.size
changeCoordinates(componentInfo.component,size.width / density, size.height / density, location.x / density,location.y / density)
}
) {
focusSwitcher.Content()
}
DisposableEffect(factory) {
componentInfo.container = document.createElement("div")
componentInfo.component = factory(document)
(root as Element).insertBefore(componentInfo.container, (root as Element).firstChild)
componentInfo.container.append(componentInfo.component)
componentInfo.updater = Updater(componentInfo.component, update)
initializingElement(componentInfo.component)
onDispose {
(root as Element).removeChild(componentInfo.container)
componentInfo.updater.dispose()
}
}
SideEffect {
componentInfo.updater.update = update
}
}
private class Updater<T : Element>(
private val component: T,
update: (T) -> Unit
) {
private var isDisposed = false
private val snapshotObserver = SnapshotStateObserver { command ->
command()
}
private val scheduleUpdate = { _: T ->
if(isDisposed.not()) {
performUpdate()
}
}
var update: (T) -> Unit = update
set(value) {
if (field != value) {
field = value
performUpdate()
}
}
private fun performUpdate() {
snapshotObserver.observeReads(component, scheduleUpdate) {
update(component)
}
}
init {
snapshotObserver.start()
performUpdate()
}
fun dispose() {
snapshotObserver.stop()
snapshotObserver.clear()
isDisposed = true
}
}
And here is I used it in my IWebView:
@Composable
actual fun IWebView(
source: String,
isHtmlData: Boolean,
modifier: Modifier
) {
if (isHtmlData) {
HtmlView(
factory = {
createElement("iframe") as org.w3c.dom.HTMLIFrameElement
},
modifier = modifier,
update = { iframe ->
iframe.srcdoc = source
iframe.style.border = "none"
iframe.style.width = "100%"
iframe.style.height = "100%"
}
)
} else {
HtmlView(
factory = {
createElement("iframe") as org.w3c.dom.HTMLIFrameElement
},
modifier = modifier,
update = { iframe ->
iframe.src = source
iframe.style.border = "none"
iframe.style.width = "100%"
iframe.style.height = "100%"
}
)
}
}
@KevinnZou, maybe you can consider this as a solution for Web as well
Thanks to you @rohan-paudel, I used it and implemented a wasmJs support for this library
You can find it here
@amirghm Brother, before publish also make sure in iOS whole screen is used by webview... Currently in iOS it is auto audjusting to safe area..... I think from package it should support edge to edge and package user should handle if they dont need edge to edge by giving padding..... Simple
Hey @rohan-paudel , Actually I didn't changed anything for this part, but there maybe more updates, I will be checking that one as well
Currently in iOS it is auto adjusting to safe area..... for this, you need to set ignoreSafeAreafor your iOS project to let views draw full screen. check your iosApp project for this
are you talking about that bro? Already did,,,, whole app is ignoring the safe area,,,,, but if I open the screen containing WebView, than just that WebView shifts to down to safe area