Browse Source

ImageViewer: Visual improvements (#2851)

pull/2884/head
Sebastian Aigner 2 years ago committed by GitHub
parent
commit
2f9e886ea6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/platform.android.kt
  2. 10
      experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/utils/GraphicsMath.android.kt
  3. 19
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/ImageViewer.common.kt
  4. 13
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/Page.kt
  5. 0
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/platform.common.kt
  6. 2
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/style/Palette.kt
  7. 16
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/GalleryScreen.kt
  8. 107
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/MemoryScreen.kt
  9. 62
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/PreviewImage.common.kt
  10. BIN
      experimental/examples/imageviewer/shared/src/commonMain/resources/magic.png
  11. 5
      experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/plarfom.desktop.kt
  12. 7
      experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/platform.desktop.kt
  13. 6
      experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/utils/GraphicsMath.desktop.kt
  14. 2
      experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/ImageViewer.desktop.kt
  15. 15
      experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/utils/GraphicsMath.ios.kt

3
experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/plarfom.android.kt → experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/platform.android.kt

@ -1,6 +1,7 @@
package example.imageviewer package example.imageviewer
import androidx.compose.foundation.layout.displayCutoutPadding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.foundation.layout.*
actual fun Modifier.notchPadding(): Modifier = displayCutoutPadding().statusBarsPadding() actual fun Modifier.notchPadding(): Modifier = displayCutoutPadding().statusBarsPadding()

10
experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/utils/GraphicsMath.android.kt

@ -1,7 +1,11 @@
package example.imageviewer.utils package example.imageviewer.utils
import android.content.Context import android.content.Context
import android.graphics.* import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.renderscript.Allocation import android.renderscript.Allocation
import android.renderscript.Element import android.renderscript.Element
import android.renderscript.RenderScript import android.renderscript.RenderScript
@ -49,7 +53,7 @@ fun applyPixelFilter(bitmap: Bitmap): Bitmap {
var result: Bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true) var result: Bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
val w: Int = bitmap.width val w: Int = bitmap.width
val h: Int = bitmap.height val h: Int = bitmap.height
result = scaleBitmapAspectRatio(result, w / 20, h / 20) result = scaleBitmapAspectRatio(result, w / 4, h / 4)
result = scaleBitmapAspectRatio(result, w, h) result = scaleBitmapAspectRatio(result, w, h)
return result return result
@ -67,7 +71,7 @@ fun applyBlurFilter(bitmap: Bitmap, context: Context): Bitmap {
val theIntrinsic: ScriptIntrinsicBlur = val theIntrinsic: ScriptIntrinsicBlur =
ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)) ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
theIntrinsic.setRadius(25f) theIntrinsic.setRadius(3f)
theIntrinsic.setInput(tmpIn) theIntrinsic.setInput(tmpIn)
theIntrinsic.forEach(tmpOut) theIntrinsic.forEach(tmpOut)

19
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/ImageViewer.common.kt

@ -2,23 +2,23 @@ package example.imageviewer
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.with import androidx.compose.animation.with
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import example.imageviewer.model.CameraPage import example.imageviewer.model.CameraPage
import example.imageviewer.model.FullScreenPage import example.imageviewer.model.FullScreenPage
import example.imageviewer.model.GalleryPage import example.imageviewer.model.GalleryPage
import example.imageviewer.model.PhotoGallery
import example.imageviewer.model.MemoryPage import example.imageviewer.model.MemoryPage
import example.imageviewer.model.Page import example.imageviewer.model.Page
import example.imageviewer.model.PhotoGallery
import example.imageviewer.model.bigUrl import example.imageviewer.model.bigUrl
import example.imageviewer.view.CameraScreen import example.imageviewer.view.CameraScreen
import example.imageviewer.view.FullscreenImage import example.imageviewer.view.FullscreenImage
@ -48,8 +48,14 @@ internal fun ImageViewerCommon(
val previousIdx = initialState.index val previousIdx = initialState.index
val currentIdx = targetState.index val currentIdx = targetState.index
val multiplier = if (previousIdx < currentIdx) 1 else -1 val multiplier = if (previousIdx < currentIdx) 1 else -1
slideInHorizontally { w -> multiplier * w } with if (initialState.value is GalleryPage && targetState.value is MemoryPage) {
slideOutHorizontally { w -> multiplier * -1 * w } fadeIn() with fadeOut(tween(durationMillis = 500, 500))
} else if (initialState.value is MemoryPage && targetState.value is GalleryPage) {
fadeIn() with fadeOut(tween(delayMillis = 150))
} else {
slideInHorizontally { w -> multiplier * w } with
slideOutHorizontally { w -> multiplier * -1 * w }
}
}) { (index, page) -> }) { (index, page) ->
when (page) { when (page) {
is GalleryPage -> { is GalleryPage -> {
@ -82,6 +88,7 @@ internal fun ImageViewerCommon(
MemoryScreen( MemoryScreen(
memoryPage = page, memoryPage = page,
photoGallery = photoGallery, photoGallery = photoGallery,
getImage = { dependencies.imageRepository.loadContent(it.bigUrl) },
localization = dependencies.localization, localization = dependencies.localization,
onSelectRelatedMemory = { galleryId -> onSelectRelatedMemory = { galleryId ->
navigationStack.push(MemoryPage(galleryId)) navigationStack.push(MemoryPage(galleryId))

13
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/Page.kt

@ -25,14 +25,23 @@ class GalleryPage(
var galleryStyle by mutableStateOf(GalleryStyle.SQUARES) var galleryStyle by mutableStateOf(GalleryStyle.SQUARES)
fun toggleGalleryStyle() { fun toggleGalleryStyle() {
galleryStyle = if(galleryStyle == GalleryStyle.SQUARES) GalleryStyle.LIST else GalleryStyle.SQUARES galleryStyle =
if (galleryStyle == GalleryStyle.SQUARES) GalleryStyle.LIST else GalleryStyle.SQUARES
} }
var currentPictureIndex by mutableStateOf(0) var currentPictureIndex by mutableStateOf(0)
val picture get(): Picture? = photoGallery.galleryStateFlow.value.getOrNull(currentPictureIndex)?.picture val picture get(): Picture? = photoGallery.galleryStateFlow.value.getOrNull(currentPictureIndex)?.picture
val pictureId get(): GalleryId? = photoGallery.galleryStateFlow.value.getOrNull(currentPictureIndex)?.id val galleryEntry: GalleryEntryWithMetadata?
get() = photoGallery.galleryStateFlow.value.getOrNull(
currentPictureIndex
)
val pictureId
get(): GalleryId? = photoGallery.galleryStateFlow.value.getOrNull(
currentPictureIndex
)?.id
fun nextImage() { fun nextImage() {
currentPictureIndex = currentPictureIndex =

0
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/plarfom.common.kt → experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/platform.common.kt

2
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/style/Palette.kt

@ -27,7 +27,7 @@ object ImageviewerColors {
val fullScreenImageBackground = Color(0xFF19191C) val fullScreenImageBackground = Color(0xFF19191C)
val uiLightBlack = Color(25, 25, 28, 128) val uiLightBlack = Color(25, 25, 28, 180)
val textOnImage = Color.White val textOnImage = Color.White
val noteBlockBackground = Color(0xFFF3F3F4) val noteBlockBackground = Color(0xFFF3F3F4)

16
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/GalleryScreen.kt

@ -10,10 +10,7 @@ import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@ -31,7 +28,6 @@ import example.imageviewer.model.GalleryId
import example.imageviewer.model.GalleryPage import example.imageviewer.model.GalleryPage
import example.imageviewer.model.PhotoGallery import example.imageviewer.model.PhotoGallery
import example.imageviewer.model.bigUrl import example.imageviewer.model.bigUrl
import example.imageviewer.notchPadding
import example.imageviewer.style.ImageviewerColors import example.imageviewer.style.ImageviewerColors
import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
@ -42,6 +38,7 @@ enum class GalleryStyle {
LIST LIST
} }
@OptIn(ExperimentalResourceApi::class)
@Composable @Composable
internal fun GalleryScreen( internal fun GalleryScreen(
galleryPage: GalleryPage, galleryPage: GalleryPage,
@ -64,7 +61,7 @@ internal fun GalleryScreen(
Box { Box {
PreviewImage( PreviewImage(
getImage = { dependencies.imageRepository.loadContent(it.bigUrl) }, getImage = { dependencies.imageRepository.loadContent(it.bigUrl) },
picture = galleryPage.picture, onClick = { picture = galleryPage.galleryEntry, onClick = {
galleryPage.pictureId?.let(onClickPreviewPicture) galleryPage.pictureId?.let(onClickPreviewPicture)
} }
) )
@ -77,7 +74,7 @@ internal fun GalleryScreen(
}, },
) )
} }
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.TopCenter) {
when (galleryPage.galleryStyle) { when (galleryPage.galleryStyle) {
GalleryStyle.SQUARES -> SquaresGalleryView( GalleryStyle.SQUARES -> SquaresGalleryView(
pictures, pictures,
@ -107,7 +104,7 @@ private fun SquaresGalleryView(
onSelect: (GalleryId) -> Unit, onSelect: (GalleryId) -> Unit,
) { ) {
Column { Column {
Spacer(Modifier.height(1.dp)) Spacer(Modifier.height(4.dp))
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 130.dp), columns = GridCells.Adaptive(minSize = 130.dp),
verticalArrangement = Arrangement.spacedBy(1.dp), verticalArrangement = Arrangement.spacedBy(1.dp),
@ -128,8 +125,8 @@ private fun SquaresGalleryView(
@OptIn(ExperimentalResourceApi::class) @OptIn(ExperimentalResourceApi::class)
@Composable @Composable
private fun MakeNewMemoryMiniature(onClick: () -> Unit) { private fun BoxScope.MakeNewMemoryMiniature(onClick: () -> Unit) {
Column { Column(modifier = Modifier.align(Alignment.BottomCenter)) {
Box( Box(
Modifier Modifier
.clip(CircleShape) .clip(CircleShape)
@ -206,6 +203,7 @@ private fun ListGalleryView(
ScrollableColumn( ScrollableColumn(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
Spacer(modifier = Modifier.height(10.dp))
for ((idx, picWithThumb) in pictures.withIndex()) { for ((idx, picWithThumb) in pictures.withIndex()) {
val (galleryId, picture, miniature) = picWithThumb val (galleryId, picture, miniature) = picWithThumb
Miniature( Miniature(

107
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/MemoryScreen.kt

@ -29,6 +29,7 @@ import example.imageviewer.model.GalleryEntryWithMetadata
import example.imageviewer.model.GalleryId import example.imageviewer.model.GalleryId
import example.imageviewer.model.MemoryPage import example.imageviewer.model.MemoryPage
import example.imageviewer.model.PhotoGallery import example.imageviewer.model.PhotoGallery
import example.imageviewer.model.Picture
import example.imageviewer.style.ImageviewerColors import example.imageviewer.style.ImageviewerColors
import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
@ -38,6 +39,7 @@ import org.jetbrains.compose.resources.painterResource
internal fun MemoryScreen( internal fun MemoryScreen(
memoryPage: MemoryPage, memoryPage: MemoryPage,
photoGallery: PhotoGallery, photoGallery: PhotoGallery,
getImage: suspend (Picture) -> ImageBitmap,
localization: Localization, localization: Localization,
onSelectRelatedMemory: (GalleryId) -> Unit, onSelectRelatedMemory: (GalleryId) -> Unit,
onBack: () -> Unit, onBack: () -> Unit,
@ -45,6 +47,10 @@ internal fun MemoryScreen(
) { ) {
val pictures by photoGallery.galleryStateFlow.collectAsState() val pictures by photoGallery.galleryStateFlow.collectAsState()
val picture = pictures.first { it.id == memoryPage.galleryId } val picture = pictures.first { it.id == memoryPage.galleryId }
var headerImage by remember(picture) { mutableStateOf(picture.thumbnail) }
LaunchedEffect(picture) {
headerImage = getImage(picture.picture)
}
Box { Box {
val scrollState = memoryPage.scrollState val scrollState = memoryPage.scrollState
Column( Column(
@ -62,19 +68,10 @@ internal fun MemoryScreen(
}, },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
MemoryHeader(picture.thumbnail, onClick = { onHeaderClick(memoryPage.galleryId) }) MemoryHeader(headerImage, onClick = { onHeaderClick(memoryPage.galleryId) })
} }
Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Column { Column {
Headliner("Place")
val locationShape = RoundedCornerShape(10.dp)
LocationVisualizer(
Modifier.padding(horizontal = 12.dp)
.clip(locationShape)
.border(1.dp, Color.Gray, locationShape)
.fillMaxWidth()
.height(200.dp)
)
Headliner("Note") Headliner("Note")
Collapsible( Collapsible(
""" """
@ -87,6 +84,15 @@ internal fun MemoryScreen(
) )
Headliner("Related memories") Headliner("Related memories")
RelatedMemoriesVisualizer(pictures, onSelectRelatedMemory) RelatedMemoriesVisualizer(pictures, onSelectRelatedMemory)
Headliner("Place")
val locationShape = RoundedCornerShape(10.dp)
LocationVisualizer(
Modifier.padding(horizontal = 12.dp)
.clip(locationShape)
.border(1.dp, Color.Gray, locationShape)
.fillMaxWidth()
.height(200.dp)
)
Spacer(Modifier.height(50.dp)) Spacer(Modifier.height(50.dp))
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -130,13 +136,7 @@ internal fun MemoryScreen(
@Composable @Composable
private fun MemoryHeader(bitmap: ImageBitmap, onClick: () -> Unit) { private fun MemoryHeader(bitmap: ImageBitmap, onClick: () -> Unit) {
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val shadowTextStyle = LocalTextStyle.current.copy(
shadow = Shadow(
color = Color.Black,
offset = Offset(4f, 4f),
blurRadius = 4f
)
)
Box(modifier = Modifier.clickable(interactionSource, null, onClick = { onClick() })) { Box(modifier = Modifier.clickable(interactionSource, null, onClick = { onClick() })) {
Image( Image(
bitmap, bitmap,
@ -144,28 +144,53 @@ private fun MemoryHeader(bitmap: ImageBitmap, onClick: () -> Unit) {
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) )
Column( MagicButtonOverlay(onClick)
modifier = Modifier.align(Alignment.BottomStart).padding(start = 12.dp, bottom = 16.dp) MemoryTextOverlay()
) { }
Text( }
"Your Memory",
textAlign = TextAlign.Left,
color = Color.White,
fontSize = 20.sp,
modifier = Modifier.fillMaxWidth(),
fontWeight = FontWeight.SemiBold,
style = shadowTextStyle
)
Spacer(Modifier.height(5.dp))
Text( @OptIn(ExperimentalResourceApi::class)
"19th of April 2023", @Composable
textAlign = TextAlign.Left, internal fun BoxScope.MagicButtonOverlay(onClick: () -> Unit) {
color = Color.White, Column(
fontWeight = FontWeight.Normal, modifier = Modifier.align(Alignment.BottomEnd).padding(end = 12.dp, bottom = 16.dp)
style = shadowTextStyle ) {
) CircularButton(painterResource("magic.png"), onClick)
} }
}
@Composable
internal fun BoxScope.MemoryTextOverlay() {
val shadowTextStyle = LocalTextStyle.current.copy(
shadow = Shadow(
color = Color.Black.copy(0.75f),
offset = Offset(0f, 0f),
blurRadius = 4f
)
)
Column(
modifier = Modifier.align(Alignment.BottomStart).padding(start = 12.dp, bottom = 16.dp)
) {
Text(
"28. Feb",
textAlign = TextAlign.Left,
color = Color.White,
fontSize = 20.sp,
lineHeight = 22.sp,
modifier = Modifier.fillMaxWidth(),
fontWeight = FontWeight.SemiBold,
style = shadowTextStyle
)
Spacer(Modifier.height(1.dp))
Text(
"London",
textAlign = TextAlign.Left,
color = Color.White,
fontSize = 14.sp,
lineHeight = 16.sp,
fontWeight = FontWeight.Normal,
style = shadowTextStyle
)
} }
} }
@ -176,7 +201,7 @@ internal fun Collapsible(s: String) {
val text = if (isCollapsed) s.lines().first() + "... (see more)" else s val text = if (isCollapsed) s.lines().first() + "... (see more)" else s
Text( Text(
text, text,
fontSize = 12.sp, fontSize = 16.sp,
modifier = Modifier modifier = Modifier
.padding(10.dp, 0.dp) .padding(10.dp, 0.dp)
.clip(RoundedCornerShape(10.dp)) .clip(RoundedCornerShape(10.dp))
@ -190,7 +215,8 @@ internal fun Collapsible(s: String) {
) )
.clickable(interactionSource = interctionSource, indication = null) { .clickable(interactionSource = interctionSource, indication = null) {
isCollapsed = !isCollapsed isCollapsed = !isCollapsed
}, }
.fillMaxWidth(),
) )
} }
@ -212,7 +238,6 @@ internal fun RelatedMemoriesVisualizer(
) { ) {
Box( Box(
modifier = Modifier.padding(10.dp, 0.dp).clip(RoundedCornerShape(10.dp)).fillMaxWidth() modifier = Modifier.padding(10.dp, 0.dp).clip(RoundedCornerShape(10.dp)).fillMaxWidth()
.height(200.dp)
) { ) {
LazyRow( LazyRow(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),

62
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/PreviewImage.common.kt

@ -1,15 +1,15 @@
package example.imageviewer.view package example.imageviewer.view
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.Spring import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.with import androidx.compose.animation.with
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -27,59 +27,61 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import example.imageviewer.model.GalleryEntryWithMetadata
import example.imageviewer.model.Picture import example.imageviewer.model.Picture
@OptIn(ExperimentalAnimationApi::class) @OptIn(ExperimentalAnimationApi::class)
@Composable @Composable
internal fun PreviewImage( internal fun PreviewImage(
picture: Picture?, picture: GalleryEntryWithMetadata?,
onClick: () -> Unit, onClick: () -> Unit,
getImage: suspend (Picture) -> ImageBitmap getImage: suspend (Picture) -> ImageBitmap
) { ) {
Box(Modifier.fillMaxWidth().height(393.dp).background(Color.Black), contentAlignment = Alignment.Center) { val interactionSource = remember { MutableInteractionSource() }
Box(
Modifier.fillMaxWidth().height(393.dp).background(Color.Black),
contentAlignment = Alignment.Center
) {
Box( Box(
modifier = Modifier.fillMaxSize() modifier = Modifier
.clickable { onClick() }, .fillMaxSize()
.clickable(interactionSource, indication = null, onClick = onClick),
) { ) {
AnimatedContent( AnimatedContent(
targetState = picture, targetState = picture,
transitionSpec = { transitionSpec = {
slideInHorizontally( slideIntoContainer(
initialOffsetX = { it }, animationSpec = spring( towards = AnimatedContentScope.SlideDirection.Left,
dampingRatio = Spring.DampingRatioLowBouncy, animationSpec = tween(durationMillis = 500, easing = FastOutSlowInEasing)
stiffness = Spring.StiffnessLow ) with slideOutOfContainer(
) towards = AnimatedContentScope.SlideDirection.Left,
) with slideOutHorizontally( animationSpec = tween(durationMillis = 500, easing = FastOutSlowInEasing)
targetOffsetX = { -it }, animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow
)
) )
// slideInVertically(initialOffsetY = { it }) with slideOutVertically(targetOffsetY = { -it })
} }
) { currentPicture -> ) { currentPicture ->
var image by remember(currentPicture) { mutableStateOf<ImageBitmap?>(null) } var image by remember(currentPicture) { mutableStateOf(currentPicture?.thumbnail) }
LaunchedEffect(currentPicture) { LaunchedEffect(currentPicture) {
if (currentPicture != null) { if (currentPicture != null) {
image = getImage(currentPicture) image = getImage(currentPicture.picture)
} }
} }
if (image != null) { if (image != null) {
Image( Box(Modifier.fillMaxSize()) {
bitmap = image!!, Image(
contentDescription = null, bitmap = image!!,
modifier = Modifier contentDescription = null,
.fillMaxSize(), modifier = Modifier
contentScale = ContentScale.Crop .fillMaxSize(),
) contentScale = ContentScale.Crop
)
MemoryTextOverlay()
}
} else { } else {
Spacer( Spacer(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) )
} }
} }
} }
} }
} }

BIN
experimental/examples/imageviewer/shared/src/commonMain/resources/magic.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

5
experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/plarfom.desktop.kt

@ -1,5 +0,0 @@
package example.imageviewer
import androidx.compose.ui.Modifier
actual fun Modifier.notchPadding(): Modifier = this

7
experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/platform.desktop.kt

@ -0,0 +1,7 @@
package example.imageviewer
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
actual fun Modifier.notchPadding(): Modifier = Modifier.padding(top = 12.dp)

6
experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/utils/GraphicsMath.kt → experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/utils/GraphicsMath.desktop.kt

@ -51,7 +51,7 @@ fun applyPixelFilter(bitmap: BufferedImage): BufferedImage {
val w: Int = bitmap.width val w: Int = bitmap.width
val h: Int = bitmap.height val h: Int = bitmap.height
var result = scaleBitmapAspectRatio(bitmap, w / 20, h / 20) var result = scaleBitmapAspectRatio(bitmap, w / 4, h / 4)
result = scaleBitmapAspectRatio(result, w, h) result = scaleBitmapAspectRatio(result, w, h)
return result return result
@ -65,8 +65,8 @@ fun applyBlurFilter(bitmap: BufferedImage): BufferedImage {
graphics.drawImage(bitmap, 0, 0, null) graphics.drawImage(bitmap, 0, 0, null)
graphics.dispose() graphics.dispose()
val radius = 11 val radius = 3
val size = 11 val size = 3
val weight: Float = 1.0f / (size * size) val weight: Float = 1.0f / (size * size)
val matrix = FloatArray(size * size) val matrix = FloatArray(size * size)

2
experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/ImageViewer.desktop.kt

@ -58,7 +58,7 @@ fun ApplicationScope.ImageViewerDesktop() {
title = "Image Viewer", title = "Image Viewer",
state = WindowState( state = WindowState(
position = WindowPosition.Aligned(Alignment.Center), position = WindowPosition.Aligned(Alignment.Center),
size = getPreferredWindowSize(800, 1000) size = getPreferredWindowSize(720, 857)
), ),
icon = painterResource("ic_imageviewer_round.png"), icon = painterResource("ic_imageviewer_round.png"),
// https://github.com/JetBrains/compose-jb/issues/2741 // https://github.com/JetBrains/compose-jb/issues/2741

15
experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/utils/GraphicsMath.ios.kt

@ -1,6 +1,15 @@
package example.imageviewer.utils package example.imageviewer.utils
import org.jetbrains.skia.* 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( fun scaleBitmapAspectRatio(
bitmap: Bitmap, bitmap: Bitmap,
@ -50,7 +59,7 @@ fun applyPixelFilter(bitmap: Bitmap): Bitmap {
val width = bitmap.width val width = bitmap.width
val height = bitmap.height val height = bitmap.height
var result = scaleBitmapAspectRatio(bitmap, width / 20, height / 20) var result = scaleBitmapAspectRatio(bitmap, width / 4, height / 4)
result = scaleBitmapAspectRatio(result, width, height) result = scaleBitmapAspectRatio(result, width, height)
return result return result
@ -61,7 +70,7 @@ fun applyBlurFilter(bitmap: Bitmap): Bitmap {
allocN32Pixels(bitmap.width, bitmap.height) allocN32Pixels(bitmap.width, bitmap.height)
} }
val blur = Paint().apply { val blur = Paint().apply {
imageFilter = ImageFilter.makeBlur(10f, 10f, FilterTileMode.CLAMP) imageFilter = ImageFilter.makeBlur(3f, 3f, FilterTileMode.CLAMP)
} }
val canvas = Canvas(result) val canvas = Canvas(result)

Loading…
Cancel
Save