milan jurkulák il y a 2 ans
Parent
commit
56e3458396

+ 8 - 2
build.gradle.kts

@@ -71,6 +71,8 @@ allprojects {
         implementation("io.github.alexzhirkevich:qrose:1.0.1")
         // connectivity
         implementation("dev.tmapps:konnection:1.4.1")
+        // web
+        implementation("io.github.kevinnzou:compose-webview-multiplatform:1.9.40-alpha01")
         // bar code
 //        implementation("io.github.alexzhirkevich:qrose-oned:1.0.1")
         // flow extensions
@@ -116,8 +118,12 @@ fun Project.createTask(
 compose {
     desktop {
         application {
-//            jvmArgs("--add-opens", "java.desktop/sun.awt=ALL-UNNAMED")
-//            jvmArgs("--add-opens", "java.desktop/java.awt.peer=ALL-UNNAMED")
+            jvmArgs("--add-opens", "java.desktop/sun.awt=ALL-UNNAMED")
+            jvmArgs("--add-opens", "java.desktop/java.awt.peer=ALL-UNNAMED")
+            if (System.getProperty("os.name").contains("Mac")) {
+                jvmArgs("--add-opens", "java.desktop/sun.lwawt=ALL-UNNAMED")
+                jvmArgs("--add-opens", "java.desktop/sun.lwawt.macosx=ALL-UNNAMED")
+            }
             mainClass = "eu.mjdev.desktop.MainKt"
             nativeDistributions {
                 packageName = libs.versions.appName.get()

+ 11 - 0
src/main/kotlin/eu/mjdev/desktop/Main.kt

@@ -3,6 +3,7 @@ package eu.mjdev.desktop
 import androidx.compose.foundation.layout.size
 import androidx.compose.material.MaterialTheme
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -14,6 +15,7 @@ import eu.mjdev.desktop.components.charts.MemoryChart
 import eu.mjdev.desktop.components.controlcenter.ControlCenter
 import eu.mjdev.desktop.components.desktoppanel.DesktopPanel
 import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
+import eu.mjdev.desktop.components.web.ComposeWebView
 import eu.mjdev.desktop.extensions.Compose.asyncImageLoader
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
@@ -60,11 +62,15 @@ fun main() = application(
                 imageLoader = asyncImageLoader()
             ),
         ) {
+            val api: DesktopProvider = LocalDesktop.current
             MainWindow(
                 panelState = panelState,
                 controlCenterState = controlCenterState,
                 menuState = menuState
             ) {
+                ComposeWebView(
+                    modifier = Modifier.size(640.dp, 480.dp).align(Alignment.Center)
+                )
                 MemoryChart(
                     modifier = Modifier.size(350.dp, 300.dp).align(Alignment.BottomEnd)
                 )
@@ -85,6 +91,11 @@ fun main() = application(
                     onFocusChange = { focus -> handleControlCenterFocus(focus) }
                 )
             }
+            DisposableEffect(Unit) {
+                onDispose {
+                    api.dispose()
+                }
+            }
         }
     }
 }

+ 7 - 2
src/main/kotlin/eu/mjdev/desktop/components/draggable/DraggableView.kt

@@ -4,12 +4,15 @@ import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.detectDragGestures
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.*
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.unit.dp
 
 @Composable
 fun DraggableView(
@@ -17,6 +20,7 @@ fun DraggableView(
     dragEnabled: Boolean = true,
     backgroundColor: Color = Color.Transparent,
     dragBackgroundColor: Color = Color.White.copy(alpha = 0.3f),
+    contentPadding: PaddingValues = PaddingValues(0.dp),
     content: @Composable BoxScope.() -> Unit = {}
 ) {
     var position by remember { mutableStateOf(Offset.Zero) }
@@ -42,10 +46,11 @@ fun DraggableView(
                         isDragging = false
                     }
                 )
-            },
+            }.padding(contentPadding),
         content = {
             Box(
-                modifier = Modifier.background(if (isDragging) dragBackgroundColor else backgroundColor)
+                modifier = Modifier
+                    .background(if (isDragging) dragBackgroundColor else backgroundColor)
             ) {
                 content()
             }

+ 10 - 12
src/main/kotlin/eu/mjdev/desktop/components/video/VideoPlayerView.kt

@@ -6,7 +6,6 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.layout.ContentScale
-import java.io.File
 
 @Suppress("UNUSED_PARAMETER")
 @Composable
@@ -20,16 +19,15 @@ fun VideoPlayerView(
     contentScale: ContentScale = ContentScale.Crop,
     onFail: (Throwable) -> Unit = { e -> e.printStackTrace() }
 ) = Box(
-    modifier = modifier,
-    contentAlignment = Alignment.Center
+    modifier = modifier
 ) {
-    val mediaUrl = when (src) {
-        is String -> src
-        is File -> src.absolutePath
-        else -> src.toString()
-    }
-    VideoPlayerFFMpeg(
-        modifier = Modifier.align(alignment),
-        file = mediaUrl
-    )
+    // todo
+//    VideoPlayerFFMpeg(
+//        modifier = Modifier.fillMaxSize().align(alignment),
+//        file = when (src) {
+//            is String -> src
+//            is File -> src.absolutePath
+//            else -> src.toString()
+//        }
+//    )
 }

+ 85 - 25
src/main/kotlin/eu/mjdev/desktop/components/web/ComposeWebView.kt

@@ -1,38 +1,98 @@
 package eu.mjdev.desktop.components.web
 
-import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.LinearProgressIndicator
+import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.awt.SwingPanel
 import androidx.compose.ui.graphics.Color
-import javax.swing.JEditorPane
-import javax.swing.JScrollPane
+import androidx.compose.ui.unit.dp
+import com.multiplatform.webview.jsbridge.IJsMessageHandler
+import com.multiplatform.webview.jsbridge.WebViewJsBridge
+import com.multiplatform.webview.jsbridge.rememberWebViewJsBridge
+import com.multiplatform.webview.web.LoadingState
+import com.multiplatform.webview.web.WebContent
+import com.multiplatform.webview.web.WebView
+import com.multiplatform.webview.web.WebViewState
+import eu.mjdev.desktop.components.draggable.DraggableView
+import eu.mjdev.desktop.extensions.Compose.DarkBlue
+import eu.mjdev.desktop.provider.DesktopProvider
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
-@Suppress("FunctionName")
-@Preview
+// todo improve
+@Suppress("LocalVariableName")
 @Composable
 fun ComposeWebView(
     modifier: Modifier = Modifier,
-    backgroundColor: Color = Color.White,
-    url: String = "https://google.com",
-    onLinkClicked: (w: JEditorPane, href: String?) -> Unit = { w, href ->
-        w.setPage(href)
-    }
-) = SwingPanel(
-    background = backgroundColor,
+    url: String = "https://www.youtube.com/watch?v=BwY6LD7KAQk",
+    contentPadding: PaddingValues = PaddingValues(top = 16.dp, start = 2.dp, end = 2.dp, bottom = 2.dp),
+    dragEnabled: Boolean = false,
+    backgroundColor: Color = Color.Transparent,
+    dragBackgroundColor: Color = Color.White.copy(alpha = 0.3f),
+    loadingLineColor: Color = Color.DarkBlue,
+    jsBridge: IJsMessageHandler? = null,
+    disablePopupWindows: Boolean = true,
+    api: DesktopProvider = LocalDesktop.current
+) = DraggableView(
     modifier = modifier,
-    factory = {
-        val jed = JEditorPane().apply {
-            isEditable = false
-            addHyperlinkListener { hev ->
-                onLinkClicked(this@apply, hev?.url?.toString())
+    contentPadding = contentPadding,
+    dragEnabled = dragEnabled,
+    backgroundColor = backgroundColor,
+    dragBackgroundColor = dragBackgroundColor
+) {
+    val webViewState = remember(url) {
+        if (url.startsWith("http")) WebViewState(WebContent.Url(url))
+        else WebViewState(WebContent.Url("file://$url"))
+    }
+    val loadingState = remember(webViewState) { webViewState.loadingState }
+    val _jsBridge: WebViewJsBridge = rememberWebViewJsBridge()
+    if (api.kcefHelper.restartRequired.value) {
+        Text(text = "Restart required.")
+    } else {
+        if (api.kcefHelper.initialized.value) {
+            WebView(
+                modifier = modifier,
+                state = webViewState
+            )
+            if (loadingState is LoadingState.Loading) {
+                LinearProgressIndicator(
+                    progress = loadingState.progress,
+                    color = loadingLineColor,
+                    modifier = Modifier.fillMaxWidth().align(Alignment.BottomStart)
+                )
+            }
+        } else {
+            Box(
+                modifier = Modifier.matchParentSize()
+                    .padding(8.dp)
+            ) {
+                CircularProgressIndicator(
+                    modifier = Modifier.fillMaxSize(),
+                    color = Color.White,
+                    strokeWidth = 10.dp,
+                    backgroundColor = Color.Transparent
+                )
+                Text(
+                    modifier = Modifier.align(Alignment.Center),
+                    text = "${api.kcefHelper.downloading.value}%"
+                )
+            }
+        }
+    }
+    LaunchedEffect(Unit) {
+        api.kcefHelper.init()
+        if (jsBridge != null) _jsBridge.register(jsBridge)
+        webViewState.webSettings.apply {
+            isJavaScriptEnabled = true
+            with(desktopWebSettings) {
+//                offScreenRendering = true
+                transparent = true
+                this.disablePopupWindows = disablePopupWindows
             }
-            setPage(url)
         }
-        JScrollPane(
-            jed,
-            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
-            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS
-        )
     }
-)
+}

+ 7 - 4
src/main/kotlin/eu/mjdev/desktop/extensions/Compose.kt

@@ -19,18 +19,21 @@ import kotlinx.coroutines.CoroutineScope
 
 object Compose {
 
-    val Color.Companion.SuperDarkGray: Color
+    val Color.Companion.SuperDarkGray
         get() = Color(0xff202020)
 
-    val Color.Companion.DarkDarkGray: Color
+    val Color.Companion.DarkDarkGray
         get() = Color(0xff404040)
 
-    val Color.Companion.MediumDarkGray: Color
+    val Color.Companion.MediumDarkGray
         get() = Color(0xff808080)
 
-    val Color.Companion.LiteDarkGray: Color
+    val Color.Companion.LiteDarkGray
         get() = Color(0xffc0c0c0)
 
+    val Color.Companion.DarkBlue
+        get() = Color(0xff202060)
+
     val PaddingValues.width: Dp
         get() = calculateLeftPadding(LayoutDirection.Ltr) + calculateRightPadding(LayoutDirection.Ltr)
 

+ 46 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/KCEFHelper.kt

@@ -0,0 +1,46 @@
+package eu.mjdev.desktop.helpers
+
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import dev.datlag.kcef.KCEF
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.io.File
+import kotlin.math.max
+
+class KCEFHelper(
+    private val scope: CoroutineScope?,
+    val initialized: MutableState<Boolean> = mutableStateOf(false),
+    val restartRequired: MutableState<Boolean> = mutableStateOf(false),
+    val downloading: MutableState<Int> = mutableStateOf(0)
+) {
+    fun init() {
+        if (!initialized.value && !restartRequired.value) {
+            scope?.launch(Dispatchers.IO) {
+                KCEF.init(builder = {
+                    installDir(File("kcef-bundle"))
+                    progress {
+                        onDownloading {
+                            downloading.value = max(it, 0F).toInt()
+                        }
+                        onInitialized {
+                            initialized.value = true
+                        }
+                    }
+                    settings {
+                        cachePath = File("cache").absolutePath
+                    }
+                }, onError = { err ->
+                    err?.printStackTrace()
+                }, onRestartRequired = {
+                    restartRequired.value = true
+                })
+            }
+        }
+    }
+
+    fun dispose() {
+        KCEF.disposeBlocking()
+    }
+}

+ 7 - 1
src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt

@@ -10,6 +10,7 @@ import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
 import eu.mjdev.desktop.components.controlcenter.pages.*
 import eu.mjdev.desktop.data.User
 import eu.mjdev.desktop.helpers.ConnectivityManager
+import eu.mjdev.desktop.helpers.KCEFHelper
 import eu.mjdev.desktop.helpers.ResourceStream
 import eu.mjdev.desktop.provider.providers.AppsProvider
 import kotlinx.coroutines.CoroutineScope
@@ -27,7 +28,8 @@ class DesktopProvider(
     val scope: CoroutineScope? = null,
     val imageLoader: ImageLoader? = null,
     val connection: ConnectivityManager = ConnectivityManager(),
-    private val scriptManager: ScriptEngineManager = ScriptEngineManager()
+    val scriptManager: ScriptEngineManager = ScriptEngineManager(),
+    val kcefHelper: KCEFHelper = KCEFHelper(scope)
 ) {
     private val __currentUser: User by lazy { User.load() }
     private val _currentUser: MutableState<User> = mutableStateOf(__currentUser)
@@ -78,6 +80,10 @@ class DesktopProvider(
         currentUser.theme.controlCenterExpandedWidth = containerSize.width.div(4)
     }
 
+    fun dispose() {
+        kcefHelper.dispose()
+    }
+
     private fun loadKey(key: String): String = runCatching {
         ResourceStream("keys/$key.key").string
     }.getOrNull().orEmpty()