Artem Kobzar
7 months ago
committed by
GitHub
31 changed files with 3643 additions and 0 deletions
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 4.3 MiB |
@ -0,0 +1,11 @@
|
||||
package example.imageviewer |
||||
|
||||
import kotlinx.browser.window |
||||
|
||||
actual fun getCurrentLanguage(): AvailableLanguages = |
||||
when (window.navigator.languages.firstOrNull() ?: window.navigator.language) { |
||||
"de" -> AvailableLanguages.DE |
||||
else -> AvailableLanguages.EN |
||||
} |
||||
|
||||
actual fun getCurrentPlatform(): String = "Web JS" |
@ -0,0 +1,8 @@
|
||||
package example.imageviewer |
||||
|
||||
import androidx.compose.ui.graphics.ImageBitmap |
||||
import androidx.compose.ui.graphics.toComposeImageBitmap |
||||
import org.jetbrains.skia.Image |
||||
|
||||
actual fun ByteArray.toImageBitmap(): ImageBitmap = |
||||
Image.makeFromEncoded(this).toComposeImageBitmap() |
@ -0,0 +1,36 @@
|
||||
package example.imageviewer |
||||
|
||||
import androidx.compose.ui.graphics.ImageBitmap |
||||
import example.imageviewer.model.PictureData |
||||
|
||||
// TODO: Rework it with some web service to store the images |
||||
class WebImageStorage : ImageStorage { |
||||
private val pictures = HashMap<String, SavedPicture>() |
||||
|
||||
override fun saveImage(picture: PictureData.Camera, image: PlatformStorableImage) { |
||||
pictures[picture.id] = SavedPicture(picture, image.imageBitmap) |
||||
} |
||||
|
||||
override fun delete(picture: PictureData.Camera) { |
||||
pictures.remove(picture.id) |
||||
} |
||||
|
||||
override fun rewrite(picture: PictureData.Camera) { |
||||
pictures[picture.id]?.let { |
||||
pictures[picture.id] = it.copy(data = picture) |
||||
} |
||||
} |
||||
|
||||
override suspend fun getThumbnail(picture: PictureData.Camera): ImageBitmap { |
||||
return pictures[picture.id]?.bitmap ?: error("Picture was not found") |
||||
} |
||||
|
||||
override suspend fun getImage(picture: PictureData.Camera): ImageBitmap { |
||||
return pictures[picture.id]?.bitmap ?: error("Picture was not found") |
||||
} |
||||
|
||||
private data class SavedPicture( |
||||
val data: PictureData, |
||||
val bitmap: ImageBitmap |
||||
) |
||||
} |
@ -0,0 +1,13 @@
|
||||
package example.imageviewer |
||||
|
||||
import androidx.compose.runtime.MutableState |
||||
import example.imageviewer.view.ToastState |
||||
|
||||
class WebPopupNotification( |
||||
private val toastState: MutableState<ToastState>, |
||||
localization: Localization |
||||
) : PopupNotification(localization) { |
||||
override fun showPopUpMessage(text: String) { |
||||
toastState.value = ToastState.Shown(text) |
||||
} |
||||
} |
@ -0,0 +1,10 @@
|
||||
package example.imageviewer |
||||
|
||||
import example.imageviewer.filter.PlatformContext |
||||
import example.imageviewer.model.PictureData |
||||
|
||||
class WebSharePicture : SharePicture { |
||||
override fun share(context: PlatformContext, picture: PictureData) { |
||||
error("Should not be called") |
||||
} |
||||
} |
@ -0,0 +1,24 @@
|
||||
package example.imageviewer.filter |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.ui.graphics.* |
||||
import example.imageviewer.utils.applyBlurFilter |
||||
import example.imageviewer.utils.applyGrayScaleFilter |
||||
import example.imageviewer.utils.applyPixelFilter |
||||
|
||||
actual fun grayScaleFilter(bitmap: ImageBitmap, context: PlatformContext): ImageBitmap { |
||||
return applyGrayScaleFilter(bitmap.asSkiaBitmap()).asComposeImageBitmap() |
||||
} |
||||
|
||||
actual fun pixelFilter(bitmap: ImageBitmap, context: PlatformContext): ImageBitmap { |
||||
return applyPixelFilter(bitmap.asSkiaBitmap()).asComposeImageBitmap() |
||||
} |
||||
|
||||
actual fun blurFilter(bitmap: ImageBitmap, context: PlatformContext): ImageBitmap { |
||||
return applyBlurFilter(bitmap.asSkiaBitmap()).asComposeImageBitmap() |
||||
} |
||||
|
||||
actual class PlatformContext |
||||
|
||||
@Composable |
||||
actual fun getPlatformContext(): PlatformContext = PlatformContext() |
@ -0,0 +1,9 @@
|
||||
package example.imageviewer.model |
||||
|
||||
actual class MemoryPage actual constructor(actual val pictureIndex: Int) : Page |
||||
|
||||
actual class CameraPage : Page |
||||
|
||||
actual class FullScreenPage actual constructor(actual val pictureIndex: Int) : Page |
||||
|
||||
actual class GalleryPage : Page |
@ -0,0 +1,22 @@
|
||||
package example.imageviewer |
||||
|
||||
import androidx.compose.material.icons.Icons |
||||
import androidx.compose.material.icons.filled.Share |
||||
import androidx.compose.ui.graphics.ImageBitmap |
||||
import androidx.compose.ui.graphics.vector.ImageVector |
||||
import example.imageviewer.utils.UUID |
||||
import kotlinx.coroutines.Dispatchers |
||||
|
||||
class WebStorableImage( |
||||
val imageBitmap: ImageBitmap |
||||
) |
||||
|
||||
actual typealias PlatformStorableImage = WebStorableImage |
||||
|
||||
actual val ioDispatcher = Dispatchers.Default |
||||
|
||||
actual val isShareFeatureSupported: Boolean = false |
||||
|
||||
actual val shareIcon: ImageVector = Icons.Filled.Share |
||||
|
||||
actual fun createUUID(): String = UUID.v4() |
@ -0,0 +1,84 @@
|
||||
package example.imageviewer.utils |
||||
|
||||
import org.jetbrains.skia.Bitmap |
||||
import org.jetbrains.skia.Canvas |
||||
import org.jetbrains.skia.ColorAlphaType |
||||
import org.jetbrains.skia.ColorInfo |
||||
import org.jetbrains.skia.ColorType |
||||
import org.jetbrains.skia.FilterTileMode |
||||
import org.jetbrains.skia.Image |
||||
import org.jetbrains.skia.ImageFilter |
||||
import org.jetbrains.skia.ImageInfo |
||||
import org.jetbrains.skia.Paint |
||||
|
||||
fun scaleBitmapAspectRatio( |
||||
bitmap: Bitmap, |
||||
width: Int, |
||||
height: Int |
||||
): Bitmap { |
||||
val boundWidth = width.toFloat() |
||||
val boundHeight = height.toFloat() |
||||
|
||||
val ratioX = boundWidth / bitmap.width |
||||
val ratioY = boundHeight / bitmap.height |
||||
val ratio = if (ratioX < ratioY) ratioX else ratioY |
||||
|
||||
val resultWidth = (bitmap.width * ratio).toInt() |
||||
val resultHeight = (bitmap.height * ratio).toInt() |
||||
|
||||
val result = Bitmap().apply { |
||||
allocN32Pixels(resultWidth, resultHeight) |
||||
} |
||||
val canvas = Canvas(result) |
||||
canvas.drawImageRect(Image.makeFromBitmap(bitmap), result.bounds.toRect()) |
||||
canvas.readPixels(result, 0, 0) |
||||
canvas.close() |
||||
|
||||
return result |
||||
} |
||||
|
||||
fun applyGrayScaleFilter(bitmap: Bitmap): Bitmap { |
||||
val imageInfo = ImageInfo( |
||||
width = bitmap.width, |
||||
height = bitmap.height, |
||||
colorInfo = ColorInfo(ColorType.GRAY_8, ColorAlphaType.PREMUL, null) |
||||
) |
||||
val result = Bitmap().apply { |
||||
allocPixels(imageInfo) |
||||
} |
||||
|
||||
val canvas = Canvas(result) |
||||
canvas.drawImageRect(Image.makeFromBitmap(bitmap), bitmap.bounds.toRect()) |
||||
canvas.readPixels(result, 0, 0) |
||||
canvas.close() |
||||
|
||||
return result |
||||
} |
||||
|
||||
fun applyPixelFilter(bitmap: Bitmap): Bitmap { |
||||
val width = bitmap.width |
||||
val height = bitmap.height |
||||
|
||||
var result = scaleBitmapAspectRatio(bitmap, width / 4, height / 4) |
||||
result = scaleBitmapAspectRatio(result, width, height) |
||||
|
||||
return result |
||||
} |
||||
|
||||
fun applyBlurFilter(bitmap: Bitmap): Bitmap { |
||||
val result = Bitmap().apply { |
||||
allocN32Pixels(bitmap.width, bitmap.height) |
||||
} |
||||
val blur = Paint().apply { |
||||
imageFilter = ImageFilter.makeBlur(3f, 3f, FilterTileMode.CLAMP) |
||||
} |
||||
|
||||
val canvas = Canvas(result) |
||||
canvas.saveLayer(null, blur) |
||||
canvas.drawImageRect(Image.makeFromBitmap(bitmap), bitmap.bounds.toRect()) |
||||
canvas.restore() |
||||
canvas.readPixels(result, 0, 0) |
||||
canvas.close() |
||||
|
||||
return result |
||||
} |
@ -0,0 +1,6 @@
|
||||
package example.imageviewer.utils |
||||
|
||||
@JsModule("uuid") |
||||
external object UUID { |
||||
fun v4(): String |
||||
} |
@ -0,0 +1,64 @@
|
||||
package example.imageviewer.view |
||||
|
||||
import androidx.compose.foundation.Image |
||||
import androidx.compose.foundation.background |
||||
import androidx.compose.foundation.layout.* |
||||
import androidx.compose.foundation.shape.RoundedCornerShape |
||||
import androidx.compose.material.Text |
||||
import androidx.compose.runtime.* |
||||
import androidx.compose.ui.Alignment |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.graphics.Color |
||||
import androidx.compose.ui.graphics.ImageBitmap |
||||
import androidx.compose.ui.unit.dp |
||||
import example.imageviewer.* |
||||
import example.imageviewer.icon.IconPhotoCamera |
||||
import example.imageviewer.model.PictureData |
||||
import example.imageviewer.model.createCameraPictureData |
||||
import imageviewer.shared.generated.resources.Res |
||||
|
||||
@Composable |
||||
actual fun CameraView( |
||||
modifier: Modifier, |
||||
onCapture: (picture: PictureData.Camera, image: PlatformStorableImage) -> Unit |
||||
) { |
||||
val randomPicture = remember { resourcePictures.random() } |
||||
var imageBitmap by remember { mutableStateOf(ImageBitmap(1, 1)) } |
||||
LaunchedEffect(randomPicture) { |
||||
imageBitmap = Res.readBytes(randomPicture.resource).toImageBitmap() |
||||
} |
||||
Box(Modifier.fillMaxSize().background(Color.Black)) { |
||||
Image( |
||||
bitmap = imageBitmap, |
||||
contentDescription = "Camera stub", |
||||
Modifier.fillMaxSize() |
||||
) |
||||
Text( |
||||
text = """ |
||||
Camera is not available on Web for now. |
||||
Instead, we will use a random picture. |
||||
""".trimIndent(), |
||||
color = Color.White, |
||||
modifier = Modifier.align(Alignment.Center) |
||||
.background( |
||||
color = Color.Black.copy(alpha = 0.7f), |
||||
shape = RoundedCornerShape(10.dp) |
||||
) |
||||
.padding(20.dp) |
||||
) |
||||
val nameAndDescription = createNewPhotoNameAndDescription() |
||||
CircularButton( |
||||
imageVector = IconPhotoCamera, |
||||
modifier = Modifier.align(Alignment.BottomCenter).padding(36.dp), |
||||
) { |
||||
onCapture( |
||||
createCameraPictureData( |
||||
name = nameAndDescription.name, |
||||
description = nameAndDescription.description, |
||||
gps = randomPicture.gps |
||||
), |
||||
WebStorableImage(imageBitmap) |
||||
) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,85 @@
|
||||
package example.imageviewer.view |
||||
|
||||
import androidx.compose.foundation.background |
||||
import androidx.compose.foundation.clickable |
||||
import androidx.compose.foundation.layout.Box |
||||
import androidx.compose.foundation.layout.BoxScope |
||||
import androidx.compose.foundation.layout.Column |
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.foundation.layout.fillMaxWidth |
||||
import androidx.compose.foundation.layout.padding |
||||
import androidx.compose.foundation.shape.RoundedCornerShape |
||||
import androidx.compose.material.AlertDialog |
||||
import androidx.compose.material.LocalTextStyle |
||||
import androidx.compose.material.TextField |
||||
import androidx.compose.material.TextFieldDefaults |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.runtime.mutableStateOf |
||||
import androidx.compose.runtime.remember |
||||
import androidx.compose.runtime.setValue |
||||
import androidx.compose.ui.Alignment |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.draw.clip |
||||
import androidx.compose.ui.graphics.Color |
||||
import androidx.compose.ui.text.font.FontWeight |
||||
import androidx.compose.ui.text.style.TextAlign |
||||
import androidx.compose.ui.unit.dp |
||||
import androidx.compose.ui.unit.sp |
||||
|
||||
@Composable |
||||
actual fun BoxScope.EditMemoryDialog( |
||||
previousName: String, |
||||
previousDescription: String, |
||||
save: (name: String, description: String) -> Unit |
||||
) { |
||||
var name by remember { mutableStateOf(previousName) } |
||||
var description by remember { mutableStateOf(previousDescription) } |
||||
AlertDialog( |
||||
onDismissRequest = { |
||||
save(name, description) |
||||
}, |
||||
buttons = { |
||||
Column( |
||||
modifier = Modifier |
||||
.align(Alignment.Center) |
||||
.padding(30.dp) |
||||
.clip(RoundedCornerShape(20.dp)) |
||||
.background(Color.White), |
||||
horizontalAlignment = Alignment.CenterHorizontally, |
||||
) { |
||||
TextField( |
||||
value = name, |
||||
onValueChange = { name = it }, |
||||
modifier = Modifier.fillMaxWidth(), |
||||
colors = TextFieldDefaults.textFieldColors( |
||||
backgroundColor = Color.White, |
||||
), |
||||
textStyle = LocalTextStyle.current.copy( |
||||
textAlign = TextAlign.Center, |
||||
fontSize = 20.sp, |
||||
fontWeight = FontWeight.Bold, |
||||
), |
||||
) |
||||
TextField( |
||||
value = description, |
||||
onValueChange = { description = it }, |
||||
colors = TextFieldDefaults.textFieldColors( |
||||
backgroundColor = Color.White, |
||||
unfocusedIndicatorColor = Color.Transparent, |
||||
focusedIndicatorColor = Color.Transparent, |
||||
) |
||||
) |
||||
} |
||||
}, |
||||
) |
||||
Box( |
||||
Modifier |
||||
.fillMaxSize() |
||||
.background(Color.Black.copy(alpha = 0.4f)) |
||||
.clickable { |
||||
|
||||
} |
||||
) { |
||||
} |
||||
} |
@ -0,0 +1,48 @@
|
||||
package example.imageviewer.view |
||||
|
||||
import androidx.compose.foundation.VerticalScrollbar |
||||
import androidx.compose.foundation.layout.Arrangement |
||||
import androidx.compose.foundation.layout.Box |
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.foundation.lazy.grid.GridCells |
||||
import androidx.compose.foundation.lazy.grid.LazyGridScope |
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid |
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState |
||||
import androidx.compose.foundation.rememberScrollbarAdapter |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.ui.Alignment |
||||
import androidx.compose.ui.Modifier |
||||
|
||||
// On the desktop, include a scrollbar |
||||
@Composable |
||||
actual fun GalleryLazyVerticalGrid( |
||||
columns: GridCells, |
||||
modifier: Modifier, |
||||
verticalArrangement: Arrangement.Vertical, |
||||
horizontalArrangement: Arrangement.Horizontal, |
||||
content: LazyGridScope.() -> Unit |
||||
) { |
||||
Box( |
||||
modifier = modifier |
||||
) { |
||||
val scrollState = rememberLazyGridState() |
||||
val adapter = rememberScrollbarAdapter(scrollState) |
||||
LazyVerticalGrid( |
||||
columns = columns, |
||||
modifier = Modifier.fillMaxSize(), |
||||
state = scrollState, |
||||
verticalArrangement = verticalArrangement, |
||||
horizontalArrangement = horizontalArrangement, |
||||
content = content |
||||
) |
||||
|
||||
Box( |
||||
modifier = Modifier.matchParentSize() |
||||
){ |
||||
VerticalScrollbar( |
||||
adapter = adapter, |
||||
modifier = Modifier.align(Alignment.CenterEnd), |
||||
) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,26 @@
|
||||
package example.imageviewer.view |
||||
|
||||
import androidx.compose.foundation.Image |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.MutableState |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.layout.ContentScale |
||||
import example.imageviewer.model.GpsPosition |
||||
import imageviewer.shared.generated.resources.Res |
||||
import imageviewer.shared.generated.resources.dummy_map |
||||
import org.jetbrains.compose.resources.painterResource |
||||
|
||||
@Composable |
||||
actual fun LocationVisualizer( |
||||
modifier: Modifier, |
||||
gps: GpsPosition, |
||||
title: String, |
||||
parentScrollEnableState: MutableState<Boolean> |
||||
) { |
||||
Image( |
||||
painter = painterResource(Res.drawable.dummy_map), |
||||
contentDescription = "Map", |
||||
contentScale = ContentScale.Crop, |
||||
modifier = modifier |
||||
) |
||||
} |
@ -0,0 +1,27 @@
|
||||
package example.imageviewer.view |
||||
|
||||
import androidx.compose.foundation.VerticalScrollbar |
||||
import androidx.compose.foundation.layout.* |
||||
import androidx.compose.foundation.rememberScrollState |
||||
import androidx.compose.foundation.rememberScrollbarAdapter |
||||
import androidx.compose.foundation.verticalScroll |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.ui.Alignment |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.unit.dp |
||||
|
||||
@Composable |
||||
actual fun ScrollableColumn(modifier: Modifier, content: @Composable ColumnScope.() -> Unit) { |
||||
val scrollState = rememberScrollState() |
||||
Box(modifier) { |
||||
Column(Modifier.verticalScroll(scrollState)) { |
||||
content() |
||||
} |
||||
VerticalScrollbar( |
||||
modifier = Modifier.align(Alignment.CenterEnd) |
||||
.padding(4.dp) |
||||
.fillMaxHeight(), |
||||
adapter = rememberScrollbarAdapter(scrollState), |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,12 @@
|
||||
package example.imageviewer.view |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
|
||||
@Composable |
||||
actual fun Tooltip( |
||||
text: String, |
||||
content: @Composable () -> Unit |
||||
) { |
||||
//No tooltip for web |
||||
content() |
||||
} |
@ -0,0 +1,22 @@
|
||||
package example.imageviewer.view |
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth |
||||
import androidx.compose.foundation.layout.padding |
||||
import androidx.compose.material.Slider |
||||
import androidx.compose.material.SliderDefaults |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.graphics.Color |
||||
import androidx.compose.ui.unit.dp |
||||
import example.imageviewer.model.ScalableState |
||||
|
||||
@Composable |
||||
actual fun ZoomControllerView(modifier: Modifier, scalableState: ScalableState) { |
||||
Slider( |
||||
modifier = modifier.fillMaxWidth(0.5f).padding(12.dp), |
||||
value = scalableState.zoom, |
||||
valueRange = scalableState.zoomLimits.start..scalableState.zoomLimits.endInclusive, |
||||
onValueChange = { scalableState.setZoom(it) }, |
||||
colors = SliderDefaults.colors(thumbColor = Color.White, activeTrackColor = Color.White) |
||||
) |
||||
} |
@ -0,0 +1,11 @@
|
||||
package example.imageviewer |
||||
|
||||
import kotlinx.browser.window |
||||
|
||||
actual fun getCurrentLanguage(): AvailableLanguages = |
||||
when (window.navigator.languages[0]?.toString() ?: window.navigator.language) { |
||||
"de" -> AvailableLanguages.DE |
||||
else -> AvailableLanguages.EN |
||||
} |
||||
|
||||
actual fun getCurrentPlatform(): String = "Web Wasm" |
@ -0,0 +1,83 @@
|
||||
import org.jetbrains.kotlin.gradle.targets.js.ir.DefaultIncrementalSyncTask |
||||
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig |
||||
|
||||
plugins { |
||||
kotlin("multiplatform") |
||||
id("org.jetbrains.compose") |
||||
} |
||||
|
||||
val copyJsResources = tasks.create("copyJsResourcesWorkaround", Copy::class.java) { |
||||
from(project(":shared").file("src/commonMain/composeResources")) |
||||
into("build/processedResources/js/main") |
||||
} |
||||
|
||||
tasks.withType<DefaultIncrementalSyncTask> { |
||||
dependsOn(copyJsResources) |
||||
} |
||||
|
||||
val copyWasmResources = tasks.create("copyWasmResourcesWorkaround", Copy::class.java) { |
||||
from(project(":shared").file("src/commonMain/composeResources")) |
||||
into("build/processedResources/wasmJs/main") |
||||
} |
||||
|
||||
afterEvaluate { |
||||
project.tasks.getByName("jsProcessResources").finalizedBy(copyJsResources) |
||||
project.tasks.getByName("wasmJsProcessResources").finalizedBy(copyWasmResources) |
||||
project.tasks.getByName("wasmJsDevelopmentExecutableCompileSync").dependsOn(copyWasmResources) |
||||
} |
||||
|
||||
val rootDirPath = project.rootDir.path |
||||
|
||||
kotlin { |
||||
js { |
||||
moduleName = "imageviewer" |
||||
browser { |
||||
commonWebpackConfig { |
||||
outputFileName = "imageviewer.js" |
||||
} |
||||
} |
||||
binaries.executable() |
||||
useEsModules() |
||||
} |
||||
|
||||
wasmJs { |
||||
moduleName = "imageviewer" |
||||
browser { |
||||
commonWebpackConfig { |
||||
devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { |
||||
static = (static ?: mutableListOf()).apply { |
||||
// Serve sources to debug inside browser |
||||
add(rootDirPath) |
||||
add(rootDirPath + "/shared/") |
||||
add(rootDirPath + "/webApp/") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
binaries.executable() |
||||
} |
||||
|
||||
sourceSets { |
||||
val jsWasmMain by creating { |
||||
dependencies { |
||||
implementation(project(":shared")) |
||||
implementation(compose.runtime) |
||||
implementation(compose.ui) |
||||
implementation(compose.foundation) |
||||
implementation(compose.material) |
||||
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) |
||||
implementation(compose.components.resources) |
||||
} |
||||
} |
||||
val jsMain by getting { |
||||
dependsOn(jsWasmMain) |
||||
} |
||||
val wasmJsMain by getting { |
||||
dependsOn(jsWasmMain) |
||||
} |
||||
} |
||||
} |
||||
|
||||
compose.experimental { |
||||
web.application {} |
||||
} |
@ -0,0 +1,12 @@
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi |
||||
import androidx.compose.ui.window.CanvasBasedWindow |
||||
import org.jetbrains.skiko.wasm.onWasmReady |
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class) |
||||
fun main() { |
||||
onWasmReady { |
||||
CanvasBasedWindow("ImageViewer") { |
||||
ImageViewerWeb() |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<title>ImageViewer</title> |
||||
<script src="skiko.js"> </script> |
||||
</head> |
||||
<body> |
||||
<canvas id="ComposeTarget"></canvas> |
||||
<script src="imageviewer.js"> </script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,34 @@
|
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.material.Surface |
||||
import androidx.compose.runtime.* |
||||
import androidx.compose.ui.Modifier |
||||
import example.imageviewer.* |
||||
import example.imageviewer.style.ImageViewerTheme |
||||
import example.imageviewer.ioDispatcher |
||||
import example.imageviewer.view.Toast |
||||
import example.imageviewer.view.ToastState |
||||
import kotlinx.coroutines.CoroutineScope |
||||
|
||||
@Composable |
||||
internal fun ImageViewerWeb() { |
||||
val toastState = remember { mutableStateOf<ToastState>(ToastState.Hidden) } |
||||
val ioScope: CoroutineScope = rememberCoroutineScope { ioDispatcher } |
||||
val dependencies = remember(ioScope) { getDependencies(toastState) } |
||||
|
||||
ImageViewerTheme { |
||||
Surface( |
||||
modifier = Modifier.fillMaxSize() |
||||
) { |
||||
ImageViewerCommon( |
||||
dependencies = dependencies |
||||
) |
||||
Toast(toastState) |
||||
} |
||||
} |
||||
} |
||||
|
||||
fun getDependencies(toastState: MutableState<ToastState>) = object : Dependencies() { |
||||
override val imageStorage: ImageStorage = WebImageStorage() |
||||
override val sharePicture = WebSharePicture() |
||||
override val notification = WebPopupNotification(toastState, localization) |
||||
} |
@ -0,0 +1,14 @@
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi |
||||
import androidx.compose.ui.window.CanvasBasedWindow |
||||
import org.jetbrains.compose.resources.ExperimentalResourceApi |
||||
import org.jetbrains.compose.resources.configureWebResources |
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class, ExperimentalResourceApi::class) |
||||
fun main() { |
||||
configureWebResources { |
||||
resourcePathMapping { path -> "./$path" } |
||||
} |
||||
CanvasBasedWindow("ImageViewer") { |
||||
ImageViewerWeb() |
||||
} |
||||
} |
Loading…
Reference in new issue