From bde57fd89160c8bb836ca57afc76b481eb473a10 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Tue, 26 Jan 2021 18:11:41 +0300 Subject: [PATCH] ImageVIewer. Splash screen, composable window API --- examples/imageviewer/build.gradle.kts | 2 +- .../example/imageviewer/view/MainScreen.kt | 91 +++++------ .../example/imageviewer/view/SplashUI.kt | 27 ++++ ...ContentState.kt => DesktopContentState.kt} | 12 +- .../example/imageviewer/utils/Application.kt | 153 ++++++++++++++++++ .../example/imageviewer/view/MainScreen.kt | 95 ++++------- .../example/imageviewer/view/Zoomable.kt | 16 +- .../src/jvmMain/kotlin/imageviewer/Main.kt | 49 ++++-- 8 files changed, 303 insertions(+), 142 deletions(-) create mode 100644 examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/view/SplashUI.kt rename examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/{ContentState.kt => DesktopContentState.kt} (99%) mode change 100755 => 100644 create mode 100644 examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/utils/Application.kt diff --git a/examples/imageviewer/build.gradle.kts b/examples/imageviewer/build.gradle.kts index 97168ac1e5..0fdbffaba4 100755 --- a/examples/imageviewer/build.gradle.kts +++ b/examples/imageviewer/build.gradle.kts @@ -11,7 +11,7 @@ buildscript { dependencies { // __LATEST_COMPOSE_RELEASE_VERSION__ - classpath("org.jetbrains.compose:compose-gradle-plugin:0.3.0-build135") + classpath("org.jetbrains.compose:compose-gradle-plugin:0.3.0-build141") classpath("com.android.tools.build:gradle:4.0.1") // __KOTLIN_COMPOSE_VERSION__ classpath(kotlin("gradle-plugin", version = "1.4.21")) diff --git a/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/view/MainScreen.kt b/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/view/MainScreen.kt index 05194155fd..53fb7f0831 100755 --- a/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/view/MainScreen.kt +++ b/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/view/MainScreen.kt @@ -1,50 +1,49 @@ package example.imageviewer.view import android.content.res.Configuration -import androidx.compose.foundation.clickable -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Image +import androidx.compose.foundation.ScrollableColumn +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.preferredHeight -import androidx.compose.foundation.layout.preferredWidth -import androidx.compose.foundation.layout.preferredSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.preferredHeight +import androidx.compose.foundation.layout.preferredSize +import androidx.compose.foundation.layout.preferredWidth +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Card -import androidx.compose.material.Divider -import androidx.compose.material.Surface import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Divider import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.asImageAsset import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import example.imageviewer.common.R import example.imageviewer.model.AppState +import example.imageviewer.model.ContentState import example.imageviewer.model.Picture import example.imageviewer.model.ScreenType -import example.imageviewer.model.ContentState import example.imageviewer.style.DarkGray import example.imageviewer.style.DarkGreen import example.imageviewer.style.Foreground -import example.imageviewer.style.Transparent -import example.imageviewer.style.MiniatureColor import example.imageviewer.style.LightGray -import example.imageviewer.style.icRefresh -import example.imageviewer.style.icEmpty +import example.imageviewer.style.MiniatureColor +import example.imageviewer.style.Transparent import example.imageviewer.style.icDots +import example.imageviewer.style.icEmpty +import example.imageviewer.style.icRefresh @Composable @@ -86,8 +85,7 @@ fun setLoadingScreen(content: ContentState) { @Composable fun setTopContent(content: ContentState) { - - setTitleBar(text = "ImageViewer", content = content) + setTitleBar(text = content.getString(R.string.app_name), content = content) if (content.getOrientation() == Configuration.ORIENTATION_PORTRAIT) { setPreviewImageUI(content) setSpacer(h = 10) @@ -102,32 +100,32 @@ fun setTitleBar(text: String, content: ContentState) { TopAppBar( backgroundColor = DarkGreen, title = { - Row(Modifier.preferredHeight(50.dp)) { - Text( - text, - color = Foreground, - modifier = Modifier.weight(1f).align(Alignment.CenterVertically) - ) - Surface( - color = Transparent, - modifier = Modifier.padding(end = 20.dp).align(Alignment.CenterVertically), - shape = CircleShape - ) { - Clickable( - onClick = { - if (content.isContentReady()) { - content.refresh() + Row(Modifier.preferredHeight(50.dp)) { + Text( + text, + color = Foreground, + modifier = Modifier.weight(1f).align(Alignment.CenterVertically) + ) + Surface( + color = Transparent, + modifier = Modifier.padding(end = 20.dp).align(Alignment.CenterVertically), + shape = CircleShape + ) { + Clickable( + onClick = { + if (content.isContentReady()) { + content.refresh() + } } + ) { + Image( + icRefresh(), + modifier = Modifier.preferredSize(35.dp) + ) } - ) { - Image( - icRefresh(), - modifier = Modifier.preferredSize(35.dp) - ) } } - } - }) + }) } @Composable @@ -145,8 +143,7 @@ fun setPreviewImageUI(content: ContentState) { Image( if (content.isMainImageEmpty()) { icEmpty() - } - else { + } else { content.getSelectedImage().asImageBitmap() }, modifier = Modifier @@ -196,14 +193,14 @@ fun setMiniatureUI( Clickable( modifier = Modifier.preferredHeight(70.dp) - .preferredWidth(30.dp), + .preferredWidth(30.dp), onClick = { showPopUpMessage( "${content.getString(R.string.picture)} " + - "${picture.name} \n" + - "${content.getString(R.string.size)} " + - "${picture.width}x${picture.height} " + - "${content.getString(R.string.pixels)}", + "${picture.name} \n" + + "${content.getString(R.string.size)} " + + "${picture.width}x${picture.height} " + + "${content.getString(R.string.pixels)}", content.getContext() ) } diff --git a/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/view/SplashUI.kt b/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/view/SplashUI.kt new file mode 100644 index 0000000000..544121d2d2 --- /dev/null +++ b/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/view/SplashUI.kt @@ -0,0 +1,27 @@ +package example.imageviewer.view + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp +import example.imageviewer.style.DarkGray + +@Composable +fun SplashUI() { + Box(Modifier.fillMaxSize().background(DarkGray)) { + Text( + // TODO implement common resources + "Image Viewer", + Modifier.align(Alignment.Center), + color = Color.White, + fontWeight = FontWeight.Bold, + fontSize = 100.sp + ) + } +} diff --git a/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/ContentState.kt b/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/DesktopContentState.kt old mode 100755 new mode 100644 similarity index 99% rename from examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/ContentState.kt rename to examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/DesktopContentState.kt index 8e629ef05c..1027da3ff0 --- a/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/ContentState.kt +++ b/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/DesktopContentState.kt @@ -1,20 +1,19 @@ package example.imageviewer.model -import java.awt.image.BufferedImage -import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import example.imageviewer.ResString import example.imageviewer.core.FilterType import example.imageviewer.model.filtration.FiltersManager -import example.imageviewer.utils.clearCache import example.imageviewer.utils.cacheImagePath +import example.imageviewer.utils.clearCache import example.imageviewer.utils.isInternetAvailable import example.imageviewer.view.showPopUpMessage -import example.imageviewer.ResString +import java.awt.image.BufferedImage import java.io.File import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import javax.swing.SwingUtilities.invokeLater -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf object ContentState { @@ -22,7 +21,6 @@ object ContentState { private lateinit var repository: ImageRepository private lateinit var uriRepository: String - @Composable fun applyContent(uriRepository: String): ContentState { if (this::uriRepository.isInitialized && this.uriRepository == uriRepository) { return this diff --git a/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/utils/Application.kt b/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/utils/Application.kt new file mode 100644 index 0000000000..4286426cea --- /dev/null +++ b/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/utils/Application.kt @@ -0,0 +1,153 @@ +package example.imageviewer.utils + +import androidx.compose.desktop.AppManager +import androidx.compose.desktop.AppWindow +import androidx.compose.desktop.WindowEvents +import androidx.compose.runtime.Applier +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.ExperimentalComposeApi +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Recomposer +import androidx.compose.runtime.compositionFor +import androidx.compose.runtime.dispatch.MonotonicFrameClock +import androidx.compose.runtime.emptyContent +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.window.MenuBar +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import java.awt.image.BufferedImage +import javax.swing.SwingUtilities +import kotlin.system.exitProcess + +fun Application( + content: @Composable ApplicationScope.() -> Unit +) = SwingUtilities.invokeLater { + AppManager.setEvents(onWindowsEmpty = null) + val scope = ApplicationScope(content) + scope.start() +} + +@OptIn(ExperimentalComposeApi::class, ExperimentalCoroutinesApi::class) +class ApplicationScope( + private val content: @Composable ApplicationScope.() -> Unit +) { + private val frameClock = ImmediateFrameClock() + private val context = Dispatchers.Main + frameClock + private val scope = CoroutineScope(context) + + private val recomposer = Recomposer(context) + private val composition = compositionFor(Unit, EmptyApplier(), recomposer) + + private val windows = mutableSetOf() + private var windowsVersion by mutableStateOf(Any()) + + fun start() { + scope.launch(start = CoroutineStart.UNDISPATCHED) { + recomposer.runRecomposeAndApplyChanges() + } + composition.setContent { + content() + WindowsMonitor() + } + } + + @Composable + private fun WindowsMonitor() { + LaunchedEffect(windowsVersion) { + if (windows.isEmpty()) { + dispose() + exitProcess(0) + } + } + } + + private fun dispose() { + composition.dispose() + scope.cancel() + } + + // TODO make parameters observable (now if any parameter is changed we don't change the window) + @Composable + fun ComposableWindow( + title: String = "JetpackDesktopWindow", + size: IntSize = IntSize(800, 600), + location: IntOffset = IntOffset.Zero, + centered: Boolean = true, + icon: BufferedImage? = null, + menuBar: MenuBar? = null, + undecorated: Boolean = false, + resizable: Boolean = true, + events: WindowEvents = WindowEvents(), + onDismissRequest: (() -> Unit)? = null, + content: @Composable () -> Unit = emptyContent() + ) { + var isOpened by remember { mutableStateOf(true) } + if (isOpened) { + DisposableEffect(Unit) { + lateinit var window: AppWindow + + fun onClose() { + if (isOpened) { + windows.remove(window) + onDismissRequest?.invoke() + windowsVersion = Any() + isOpened = false + } + } + + window = AppWindow( + title = title, + size = size, + location = location, + centered = centered, + icon = icon, + menuBar = menuBar, + undecorated = undecorated, + resizable = resizable, + events = events, + onDismissRequest = { + onClose() + } + ) + + windows.add(window) + window.show(recomposer, content) + + onDispose { + if (!window.isClosed) { + window.close() + } + onClose() + } + } + } + } +} + +private class ImmediateFrameClock : MonotonicFrameClock { + override suspend fun withFrameNanos( + onFrame: (frameTimeNanos: Long) -> R + ) = onFrame(System.nanoTime()) +} + +@OptIn(ExperimentalComposeApi::class) +private class EmptyApplier : Applier { + override val current: Unit = Unit + override fun down(node: Unit) = Unit + override fun up() = Unit + override fun insertTopDown(index: Int, instance: Unit) = Unit + override fun insertBottomUp(index: Int, instance: Unit) = Unit + override fun remove(index: Int, count: Int) = Unit + override fun move(from: Int, to: Int, count: Int) = Unit + override fun clear() = Unit +} diff --git a/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/MainScreen.kt b/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/MainScreen.kt index e4f82dc034..4b466af8b1 100755 --- a/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/MainScreen.kt +++ b/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/MainScreen.kt @@ -1,106 +1,71 @@ package example.imageviewer.view -import androidx.compose.foundation.clickable -import androidx.compose.foundation.background import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollableColumn -import androidx.compose.runtime.Composable -import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.VerticalScrollbar +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.preferredHeight -import androidx.compose.foundation.layout.preferredWidth -import androidx.compose.foundation.layout.preferredSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.rememberScrollbarAdapter +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.preferredHeight +import androidx.compose.foundation.layout.preferredSize +import androidx.compose.foundation.layout.preferredWidth import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.VerticalScrollbar -import androidx.compose.material.Surface -import androidx.compose.material.CircularProgressIndicator +import androidx.compose.foundation.rememberScrollbarAdapter +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Card +import androidx.compose.material.Divider import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.TopAppBar -import androidx.compose.material.Card -import androidx.compose.material.Divider +import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.TextUnit +import example.imageviewer.ResString import example.imageviewer.model.AppState +import example.imageviewer.model.ContentState import example.imageviewer.model.Picture import example.imageviewer.model.ScreenType -import example.imageviewer.model.ContentState import example.imageviewer.style.DarkGray import example.imageviewer.style.DarkGreen import example.imageviewer.style.Foreground -import example.imageviewer.style.Transparent +import example.imageviewer.style.LightGray +import example.imageviewer.style.MiniatureColor +import example.imageviewer.style.MiniatureHoverColor import example.imageviewer.style.TranslucentBlack import example.imageviewer.style.TranslucentWhite -import example.imageviewer.style.PreviewImageAreaHoverColor -import example.imageviewer.style.MiniatureHoverColor -import example.imageviewer.style.MiniatureColor -import example.imageviewer.style.LightGray -import example.imageviewer.style.icRefresh -import example.imageviewer.style.icEmpty +import example.imageviewer.style.Transparent import example.imageviewer.style.icDots +import example.imageviewer.style.icEmpty +import example.imageviewer.style.icRefresh import example.imageviewer.utils.toByteArray -import example.imageviewer.ResString -import org.jetbrains.skija.Image -import org.jetbrains.skija.IRect @Composable fun setMainScreen(content: ContentState) { - - if (content.isContentReady()) { - Column { - setTopContent(content) - setScrollableArea(content) - } - } else { - setLoadingScreen(content) - } -} - -@Composable -private fun setLoadingScreen(content: ContentState) { - Box { - Column { - setTopContent(content) - } - Box(modifier = Modifier.align(Alignment.Center)) { - Surface(color = DarkGray, elevation = 4.dp, shape = CircleShape) { - CircularProgressIndicator( - modifier = Modifier.preferredSize(50.dp).padding(3.dp, 3.dp, 4.dp, 4.dp), - color = DarkGreen - ) - } - } - Text( - text = ResString.loading, - modifier = Modifier.align(Alignment.Center).offset(0.dp, 70.dp), - style = MaterialTheme.typography.body1, - color = Foreground - ) + check(content.isContentReady()) + Column { + setTopContent(content) + setScrollableArea(content) } } @Composable fun setTopContent(content: ContentState) { - - setTitleBar(text = "ImageViewer", content = content) + setTitleBar(text = ResString.appName, content = content) setPreviewImageUI(content) setSpacer(h = 10) setDivider() @@ -229,7 +194,7 @@ fun setMiniatureUI( .padding(start = 16.dp), style = MaterialTheme.typography.body1 ) - + Clickable( modifier = Modifier.preferredHeight(70.dp) .preferredWidth(30.dp) diff --git a/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/Zoomable.kt b/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/Zoomable.kt index 7a7d35f9b4..1fede100f8 100644 --- a/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/Zoomable.kt +++ b/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/Zoomable.kt @@ -1,14 +1,16 @@ package example.imageviewer.view +import androidx.compose.foundation.InteractionState import androidx.compose.foundation.clickable -import androidx.compose.runtime.Composable import androidx.compose.material.Surface +import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.ui.focus.FocusReference -import androidx.compose.ui.input.key.* import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusModifier -import androidx.compose.ui.focus.focusReference +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.shortcuts import example.imageviewer.style.Transparent @Composable @@ -17,7 +19,7 @@ fun Zoomable( modifier: Modifier = Modifier, children: @Composable() () -> Unit ) { - val focusRequester = remember { FocusReference() } + val focusRequester = remember { FocusRequester() } Surface( color = Transparent, @@ -32,9 +34,9 @@ fun Zoomable( onScale.resetFactor() } } - .focusReference(focusRequester) + .focusRequester(focusRequester) .focusModifier() - .clickable(indication = null) { focusRequester.requestFocus() } + .clickable(interactionState = InteractionState(), indication = null) { focusRequester.requestFocus() } ) { children() } diff --git a/examples/imageviewer/desktop/src/jvmMain/kotlin/imageviewer/Main.kt b/examples/imageviewer/desktop/src/jvmMain/kotlin/imageviewer/Main.kt index c1a3f42192..e0edbd6936 100644 --- a/examples/imageviewer/desktop/src/jvmMain/kotlin/imageviewer/Main.kt +++ b/examples/imageviewer/desktop/src/jvmMain/kotlin/imageviewer/Main.kt @@ -1,28 +1,47 @@ package example.imageviewer import androidx.compose.desktop.DesktopTheme -import androidx.compose.desktop.Window import androidx.compose.material.MaterialTheme import androidx.compose.runtime.remember -import example.imageviewer.utils.getPreferredWindowSize -import example.imageviewer.view.BuildAppUI import example.imageviewer.model.ContentState -import example.imageviewer.model.ImageRepository import example.imageviewer.style.icAppRounded +import example.imageviewer.utils.Application +import example.imageviewer.utils.getPreferredWindowSize +import example.imageviewer.view.BuildAppUI +import example.imageviewer.view.SplashUI fun main() { + val content = ContentState.applyContent( + "https://raw.githubusercontent.com/JetBrains/compose-jb/master/artwork/imageviewerrepo/fetching.list" + ) + + Application { + val icon = remember(::icAppRounded) - Window( - title = "ImageViewer", - size = getPreferredWindowSize(800, 1000), - icon = icAppRounded() - ) { - val content = ContentState.applyContent( - "https://raw.githubusercontent.com/JetBrains/compose-jb/master/artwork/imageviewerrepo/fetching.list" - ) - MaterialTheme { - DesktopTheme { - BuildAppUI(content) + if (content.isContentReady()) { + ComposableWindow( + title = "Image Viewer", + size = getPreferredWindowSize(800, 1000), + icon = icon + ) { + MaterialTheme { + DesktopTheme { + BuildAppUI(content) + } + } + } + } else { + ComposableWindow( + title = "Image Viewer", + size = getPreferredWindowSize(800, 300), + undecorated = true, + icon = icon, + ) { + MaterialTheme { + DesktopTheme { + SplashUI() + } + } } } }