Browse Source

First steps in implementing redesigned UI (#2817)

Co-authored-by: Sebastian.Aigner <Sebastian.Aigner@de-unit-1151.fritz.box>
pull/2824/head
Sebastian Aigner 2 years ago committed by GitHub
parent
commit
2588c599ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/style/Palette.kt
  2. 111
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/FullscreenImage.kt
  3. 230
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/GalleryScreen.kt
  4. 176
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/MemoryScreen.kt
  5. 81
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/PreviewImage.common.kt
  6. BIN
      experimental/examples/imageviewer/shared/src/commonMain/resources/arrowleft.png
  7. BIN
      experimental/examples/imageviewer/shared/src/commonMain/resources/eye.png
  8. BIN
      experimental/examples/imageviewer/shared/src/commonMain/resources/plus.png
  9. BIN
      experimental/examples/imageviewer/shared/src/commonMain/resources/trash.png

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

@ -19,6 +19,16 @@ object ImageviewerColors {
val TranslucentWhite = Color(255, 255, 255, 20) val TranslucentWhite = Color(255, 255, 255, 20)
val Transparent = Color.Transparent val Transparent = Color.Transparent
val background = Color(0xFFFFFFFF)
val onBackground = Color(0xFF19191C)
val fullScreenImageBackground = Color(0xFF19191C)
val uiLightBlack = Color(25, 25, 28, 128)
val textOnImage = Color.White
val noteBlockBackground = Color(0xFFF3F3F4)
val KotlinGradient0 = Color(0xFF7F52FF) val KotlinGradient0 = Color(0xFF7F52FF)
val KotlinGradient50 = Color(0xFFC811E2) val KotlinGradient50 = Color(0xFFC811E2)
val KotlinGradient100 = Color(0xFFE54857) val KotlinGradient100 = Color(0xFFE54857)
@ -39,8 +49,8 @@ internal fun ImageViewerTheme(content: @Composable () -> Unit) {
isSystemInDarkTheme() // todo check and change colors isSystemInDarkTheme() // todo check and change colors
MaterialTheme( MaterialTheme(
colorScheme = MaterialTheme.colorScheme.copy( colorScheme = MaterialTheme.colorScheme.copy(
background = Color(0xFF1B1B1B), background = ImageviewerColors.background,
onBackground = Color(0xFFFFFFFF) onBackground = ImageviewerColors.onBackground
) )
) { ) {
content() content()

111
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/FullscreenImage.kt

@ -5,8 +5,10 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
@ -57,7 +59,7 @@ internal fun FullscreenImage(
null null
} }
} }
Box(Modifier.fillMaxSize().background(color = MaterialTheme.colorScheme.background)) { Box(Modifier.fillMaxSize().background(color = ImageviewerColors.fullScreenImageBackground)) {
Column { Column {
FullscreenImageBar( FullscreenImageBar(
localization, localization,
@ -73,31 +75,52 @@ internal fun FullscreenImage(
} }
}) })
if (imageWithFilter != null) { if (imageWithFilter != null) {
val imageSize = IntSize(imageWithFilter.width, imageWithFilter.height) Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) {
val scalableState = remember(imageSize) { ScalableState(imageSize) } val imageSize = IntSize(imageWithFilter.width, imageWithFilter.height)
val visiblePartOfImage: IntRect = scalableState.visiblePart val scalableState = remember(imageSize) { ScalableState(imageSize) }
Slider( val visiblePartOfImage: IntRect = scalableState.visiblePart
modifier = Modifier.fillMaxWidth(), Column {
value = scalableState.scale, Slider(
valueRange = MIN_SCALE..MAX_SCALE, modifier = Modifier.fillMaxWidth(),
onValueChange = { scalableState.setScale(it) }, value = scalableState.scale,
) valueRange = MIN_SCALE..MAX_SCALE,
Box( onValueChange = { scalableState.setScale(it) },
modifier = Modifier.fillMaxSize() )
.onGloballyPositioned { coordinates -> Box(
scalableState.changeBoxSize(coordinates.size) modifier = Modifier.fillMaxSize()
.onGloballyPositioned { coordinates ->
scalableState.changeBoxSize(coordinates.size)
}
.addUserInput(scalableState)
) {
Image(
modifier = Modifier.fillMaxSize(),
painter = BitmapPainter(
imageWithFilter,
srcOffset = visiblePartOfImage.topLeft,
srcSize = visiblePartOfImage.size
),
contentDescription = null
)
}
}
Box(
Modifier.clip(RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp))
.background(ImageviewerColors.fullScreenImageBackground).padding(16.dp)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(bottom = 16.dp)
) {
FilterButtons(availableFilters, selectedFilters, {
if (it !in selectedFilters) {
selectedFilters += it
} else {
selectedFilters -= it
}
})
} }
.addUserInput(scalableState) }
) {
Image(
modifier = Modifier.fillMaxSize(),
painter = BitmapPainter(
imageWithFilter,
srcOffset = visiblePartOfImage.topLeft,
srcSize = visiblePartOfImage.size
),
contentDescription = null
)
} }
} else { } else {
LoadingScreen() LoadingScreen()
@ -118,7 +141,7 @@ private fun FullscreenImageBar(
onSelectFilter: (FilterType) -> Unit onSelectFilter: (FilterType) -> Unit
) { ) {
TopAppBar( TopAppBar(
modifier = Modifier.background(brush = ImageviewerColors.kotlinHorizontalGradientBrush), modifier = Modifier.background(color = ImageviewerColors.fullScreenImageBackground),
colors = TopAppBarDefaults.smallTopAppBarColors( colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = ImageviewerColors.Transparent, containerColor = ImageviewerColors.Transparent,
titleContentColor = MaterialTheme.colorScheme.onBackground titleContentColor = MaterialTheme.colorScheme.onBackground
@ -128,27 +151,30 @@ private fun FullscreenImageBar(
}, },
navigationIcon = { navigationIcon = {
Tooltip(localization.back) { Tooltip(localization.back) {
Image( CircularButton(
painterResource("back.png"), painterResource("arrowleft.png"),
contentDescription = null, onClick = { onBack() }
modifier = Modifier.size(38.dp)
.clip(CircleShape)
.clickable { onBack() }
) )
} }
}, },
actions = {
for (type in filters) {
FilterButton(active = type in selectedFilters,
type,
onClick = {
onSelectFilter(type)
})
}
}
) )
} }
@Composable
private fun FilterButtons(
filters: List<FilterType>,
selectedFilters: Set<FilterType>,
onSelectFilter: (FilterType) -> Unit
) {
for (type in filters) {
FilterButton(active = type in selectedFilters,
type,
onClick = {
onSelectFilter(type)
})
}
}
@Composable @Composable
private fun FilterButton( private fun FilterButton(
@ -165,14 +191,13 @@ private fun FilterButton(
Image( Image(
getFilterImage(active, type = type), getFilterImage(active, type = type),
contentDescription = null, contentDescription = null,
Modifier.size(38.dp) Modifier.size(40.dp)
.hoverable(interactionSource) .hoverable(interactionSource)
.background(color = ImageviewerColors.buttonBackground(filterButtonHover)) .background(color = ImageviewerColors.buttonBackground(filterButtonHover))
.clickable { onClick() } .clickable { onClick() }
) )
} }
} }
Spacer(Modifier.width(20.dp))
} }
@OptIn(ExperimentalResourceApi::class) @OptIn(ExperimentalResourceApi::class)

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

@ -1,9 +1,7 @@
package example.imageviewer.view package example.imageviewer.view
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -15,15 +13,13 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells 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.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -32,14 +28,10 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.draw.clip
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.text.font.FontStyle
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.dp
import androidx.compose.ui.unit.sp
import example.imageviewer.Dependencies import example.imageviewer.Dependencies
import example.imageviewer.ExternalImageViewerEvent import example.imageviewer.ExternalImageViewerEvent
import example.imageviewer.model.GalleryEntryWithMetadata import example.imageviewer.model.GalleryEntryWithMetadata
@ -48,24 +40,9 @@ 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.style.ImageviewerColors import example.imageviewer.style.ImageviewerColors
import example.imageviewer.style.ImageviewerColors.kotlinHorizontalGradientBrush
import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
@Composable
internal fun GalleryHeader() {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(10.dp).fillMaxWidth()
) {
Text(
"My Gallery",
fontSize = 25.sp,
color = MaterialTheme.colorScheme.onBackground,
fontStyle = FontStyle.Italic
)
}
}
enum class GalleryStyle { enum class GalleryStyle {
SQUARES, SQUARES,
@ -91,32 +68,36 @@ internal fun GalleryScreen(
} }
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
TitleBar( Box {
onRefresh = { photoGallery.updatePictures() }, if (needShowPreview()) {
onToggle = { galleryPage.toggleGalleryStyle() }, PreviewImage(
dependencies getImage = { dependencies.imageRepository.loadContent(it.bigUrl) },
) picture = galleryPage.picture, onClick = {
if (needShowPreview()) { galleryPage.pictureId?.let(onClickPreviewPicture)
PreviewImage( })
getImage = { dependencies.imageRepository.loadContent(it.bigUrl) }, }
picture = galleryPage.picture, onClick = { TitleBar(
galleryPage.pictureId?.let(onClickPreviewPicture) onRefresh = { photoGallery.updatePictures() },
}) onToggle = { galleryPage.toggleGalleryStyle() },
} dependencies
when (galleryPage.galleryStyle) {
GalleryStyle.SQUARES -> SquaresGalleryView(
pictures,
galleryPage.pictureId,
onSelect = { galleryPage.selectPicture(it) },
onMakeNewMemory
) )
}
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) {
when (galleryPage.galleryStyle) {
GalleryStyle.SQUARES -> SquaresGalleryView(
pictures,
galleryPage.pictureId,
onSelect = { galleryPage.selectPicture(it) },
)
GalleryStyle.LIST -> ListGalleryView( GalleryStyle.LIST -> ListGalleryView(
pictures, pictures,
dependencies, dependencies,
onSelect = { galleryPage.selectPicture(it) }, onSelect = { galleryPage.selectPicture(it) },
onFullScreen = { onClickPreviewPicture(it) } onFullScreen = { onClickPreviewPicture(it) }
) )
}
MakeNewMemoryMiniature(onMakeNewMemory)
} }
} }
if (pictures.isEmpty()) { if (pictures.isEmpty()) {
@ -129,54 +110,95 @@ private fun SquaresGalleryView(
images: List<GalleryEntryWithMetadata>, images: List<GalleryEntryWithMetadata>,
selectedImage: GalleryId?, selectedImage: GalleryId?,
onSelect: (GalleryId) -> Unit, onSelect: (GalleryId) -> Unit,
onMakeNewMemory: () -> Unit,
) { ) {
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) { Column {
item { Spacer(Modifier.height(1.dp))
MakeNewMemoryMiniature(onMakeNewMemory) LazyVerticalGrid(
} columns = GridCells.Adaptive(minSize = 130.dp),
itemsIndexed(images) { idx, image -> verticalArrangement = Arrangement.spacedBy(1.dp),
val isSelected = image.id == selectedImage horizontalArrangement = Arrangement.spacedBy(1.dp)
val (picture, bitmap) = image ) {
SquareMiniature( itemsIndexed(images) { idx, image ->
image.thumbnail, val isSelected = image.id == selectedImage
onClick = { onSelect(picture) }, val (picture, bitmap) = image
isHighlighted = isSelected SquareMiniature(
) image.thumbnail,
onClick = { onSelect(picture) },
isHighlighted = isSelected
)
}
} }
} }
} }
@OptIn(ExperimentalResourceApi::class)
@Composable @Composable
private fun MakeNewMemoryMiniature(onClick: () -> Unit) { private fun MakeNewMemoryMiniature(onClick: () -> Unit) {
Box( Column {
Modifier.aspectRatio(1.0f) Box(
.clickable { Modifier
onClick() .clip(CircleShape)
}, contentAlignment = Alignment.Center .width(52.dp)
) { .background(ImageviewerColors.uiLightBlack)
Text( .aspectRatio(1.0f)
"+", .clickable {
modifier = Modifier.fillMaxWidth(), onClick()
color = MaterialTheme.colorScheme.onBackground, },
textAlign = TextAlign.Center, contentAlignment = Alignment.Center
fontSize = 50.sp ) {
) Image(
painter = painterResource("plus.png"),
contentDescription = null,
modifier = Modifier
.width(18.dp)
.height(18.dp),
)
}
Spacer(Modifier.height(32.dp))
} }
} }
@OptIn(ExperimentalResourceApi::class)
@Composable @Composable
internal fun SquareMiniature(image: ImageBitmap, isHighlighted: Boolean, onClick: () -> Unit) { internal fun SquareMiniature(image: ImageBitmap, isHighlighted: Boolean, onClick: () -> Unit) {
Image( Box(
bitmap = image, Modifier.aspectRatio(1.0f).clickable { onClick() },
contentDescription = null, contentAlignment = Alignment.BottomEnd
modifier = Modifier.aspectRatio(1.0f).clickable { onClick() }.then( ) {
if (isHighlighted) { Image(
Modifier.border(BorderStroke(5.dp, Color.White)) bitmap = image,
} else Modifier contentDescription = null,
), modifier = Modifier.fillMaxSize().clickable { onClick() }.then(
contentScale = ContentScale.Crop if (isHighlighted) {
) Modifier//.border(BorderStroke(5.dp, Color.White))
} else Modifier
),
contentScale = ContentScale.Crop
)
if (isHighlighted) {
Box(Modifier.fillMaxSize().background(ImageviewerColors.uiLightBlack))
Box(
Modifier
.padding(end = 4.dp, bottom = 4.dp)
.clip(CircleShape)
.width(32.dp)
.background(ImageviewerColors.uiLightBlack)
.aspectRatio(1.0f)
.clickable {
onClick()
},
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource("eye.png"),
contentDescription = null,
modifier = Modifier
.width(17.dp)
.height(17.dp),
)
}
}
}
} }
@Composable @Composable
@ -186,8 +208,6 @@ private fun ListGalleryView(
onSelect: (GalleryId) -> Unit, onSelect: (GalleryId) -> Unit,
onFullScreen: (GalleryId) -> Unit onFullScreen: (GalleryId) -> Unit
) { ) {
GalleryHeader()
Spacer(modifier = Modifier.height(10.dp))
ScrollableColumn( ScrollableColumn(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
@ -215,44 +235,14 @@ private fun ListGalleryView(
@Composable @Composable
private fun TitleBar(onRefresh: () -> Unit, onToggle: () -> Unit, dependencies: Dependencies) { private fun TitleBar(onRefresh: () -> Unit, onToggle: () -> Unit, dependencies: Dependencies) {
TopAppBar( TopAppBar(
modifier = Modifier.background(brush = kotlinHorizontalGradientBrush), modifier = Modifier.padding(start = 12.dp, end = 12.dp),
colors = TopAppBarDefaults.smallTopAppBarColors( colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = ImageviewerColors.Transparent, containerColor = ImageviewerColors.Transparent,
titleContentColor = MaterialTheme.colorScheme.onBackground titleContentColor = MaterialTheme.colorScheme.onBackground
), ),
title = { title = {
Row(Modifier.height(50.dp)) { Row(Modifier.height(50.dp).fillMaxWidth(), horizontalArrangement = Arrangement.End) {
Text( CircularButton(painterResource("list_view.png")) { onToggle() }
dependencies.localization.appName,
modifier = Modifier.weight(1f).align(Alignment.CenterVertically),
fontWeight = FontWeight.Bold
)
Surface(
color = ImageviewerColors.Transparent,
modifier = Modifier.padding(end = 20.dp).align(Alignment.CenterVertically),
shape = CircleShape
) {
Image(
painter = painterResource("list_view.png"),
contentDescription = null,
modifier = Modifier.size(35.dp).clickable {
onToggle()
}
)
}
Surface(
color = ImageviewerColors.Transparent,
modifier = Modifier.padding(end = 20.dp).align(Alignment.CenterVertically),
shape = CircleShape
) {
Image(
painter = painterResource("refresh.png"),
contentDescription = null,
modifier = Modifier.size(35.dp).clickable {
onRefresh()
}
)
}
} }
}) })
} }

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

@ -1,6 +1,8 @@
package example.imageviewer.view package example.imageviewer.view
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
@ -9,6 +11,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
@ -22,9 +25,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -49,38 +55,17 @@ 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 }
Column { Box {
TopAppBar(
modifier = Modifier.background(brush = ImageviewerColors.kotlinHorizontalGradientBrush),
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = ImageviewerColors.Transparent,
titleContentColor = MaterialTheme.colorScheme.onBackground
),
title = {
Text("")
},
navigationIcon = {
Tooltip("Back") {
Image(
painterResource("back.png"),
contentDescription = null,
modifier = Modifier.size(38.dp)
.clip(CircleShape)
.clickable { onBack() }
)
}
},
)
val scrollState = memoryPage.scrollState val scrollState = memoryPage.scrollState
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.verticalScroll(scrollState), .verticalScroll(scrollState)
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(300.dp) .height(393.dp)
.background(Color.White) .background(Color.White)
.graphicsLayer { .graphicsLayer {
translationY = 0.5f * scrollState.value translationY = 0.5f * scrollState.value
@ -89,11 +74,11 @@ internal fun MemoryScreen(
) { ) {
MemoryHeader(picture.thumbnail, onClick = { onHeaderClick(memoryPage.galleryId) }) MemoryHeader(picture.thumbnail, onClick = { onHeaderClick(memoryPage.galleryId) })
} }
Box(modifier = Modifier.background(ImageviewerColors.kotlinHorizontalGradientBrush)) { Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Column { Column {
Headliner("Where it happened") Headliner("Place")
LocationVisualizer() LocationVisualizer()
Headliner("What happened") Headliner("Note")
Collapsible( Collapsible(
""" """
I took a picture with my iPhone 14 at 17:45. The picture ended up being 3024 x 4032 pixels. I took a picture with my iPhone 14 at 17:45. The picture ended up being 3024 x 4032 pixels.
@ -106,22 +91,74 @@ internal fun MemoryScreen(
Headliner("Related memories") Headliner("Related memories")
RelatedMemoriesVisualizer(pictures, onSelectRelatedMemory) RelatedMemoriesVisualizer(pictures, onSelectRelatedMemory)
Spacer(Modifier.height(50.dp)) Spacer(Modifier.height(50.dp))
Text( Row(
"Delete this memory", modifier = Modifier.fillMaxWidth(),
Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(
textAlign = TextAlign.Center, 8.dp,
color = Color.White Alignment.CenterHorizontally
) ),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painterResource("trash.png"),
contentDescription = null,
modifier = Modifier.size(14.dp)
)
Text(
text = "Delete Memory",
textAlign = TextAlign.Left,
color = ImageviewerColors.onBackground,
fontSize = 14.sp,
fontWeight = FontWeight.Normal
)
}
Spacer(Modifier.height(50.dp)) Spacer(Modifier.height(50.dp))
} }
} }
} }
TopAppBar(
modifier = Modifier.padding(start = 12.dp, end = 12.dp),
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = ImageviewerColors.Transparent,
titleContentColor = MaterialTheme.colorScheme.onBackground
),
title = {
Text("")
},
navigationIcon = {
Tooltip("Back") {
CircularButton(painterResource("arrowleft.png"), onClick = { onBack() })
}
},
)
} }
} }
@Composable
internal fun CircularButton(image: Painter, onClick: () -> Unit) {
Box(
Modifier.size(40.dp).clip(CircleShape).background(ImageviewerColors.uiLightBlack)
.clickable { onClick() }, contentAlignment = Alignment.Center
) {
Image(
image,
contentDescription = null,
modifier = Modifier.size(20.dp)
)
}
}
@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,
@ -129,35 +166,27 @@ private fun MemoryHeader(bitmap: ImageBitmap, onClick: () -> Unit) {
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) )
Column(modifier = Modifier.align(Alignment.Center)) { Column(
modifier = Modifier.align(Alignment.BottomStart).padding(start = 12.dp, bottom = 16.dp)
) {
Text( Text(
"Your Memory", "Your Memory",
textAlign = TextAlign.Center, textAlign = TextAlign.Left,
color = Color.White, color = Color.White,
fontSize = 50.sp, fontSize = 20.sp,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
fontWeight = FontWeight.Black fontWeight = FontWeight.SemiBold,
style = shadowTextStyle
)
Spacer(Modifier.height(5.dp))
Text(
"19th of April 2023",
textAlign = TextAlign.Left,
color = Color.White,
fontWeight = FontWeight.Normal,
style = shadowTextStyle
) )
Spacer(Modifier.height(30.dp))
Box(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.border(
width = 2.dp,
color = Color.Black,
shape = RoundedCornerShape(100.dp)
)
.clip(
RoundedCornerShape(100.dp)
)
.background(Color.Black.copy(alpha = 0.7f)).padding(10.dp)
) {
Text(
"19th of April 2023",
textAlign = TextAlign.Center,
color = Color.White
)
}
} }
} }
} }
@ -169,12 +198,18 @@ 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,
modifier = Modifier modifier = Modifier
.padding(10.dp, 0.dp) .padding(10.dp, 0.dp)
.clip(RoundedCornerShape(10.dp)) .clip(RoundedCornerShape(10.dp))
.background(Color.White) .background(ImageviewerColors.noteBlockBackground)
.padding(10.dp) .padding(10.dp)
.animateContentSize() .animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow
)
)
.clickable(interactionSource = interctionSource, indication = null) { .clickable(interactionSource = interctionSource, indication = null) {
isCollapsed = !isCollapsed isCollapsed = !isCollapsed
}, },
@ -185,10 +220,10 @@ internal fun Collapsible(s: String) {
internal fun Headliner(s: String) { internal fun Headliner(s: String) {
Text( Text(
text = s, text = s,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.SemiBold,
fontSize = 32.sp, fontSize = 20.sp,
color = Color.White, color = Color.Black,
modifier = Modifier.padding(10.dp, 30.dp, 10.dp, 10.dp) modifier = Modifier.padding(start = 12.dp, top = 32.dp, end = 12.dp, bottom = 16.dp)
) )
} }
@ -212,7 +247,10 @@ internal fun RelatedMemoriesVisualizer(
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) .height(200.dp)
) { ) {
LazyRow(modifier = Modifier.fillMaxSize()) { LazyRow(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
itemsIndexed(ps) { idx, item -> itemsIndexed(ps) { idx, item ->
RelatedMemory(idx, item, onSelectRelatedMemory) RelatedMemory(idx, item, onSelectRelatedMemory)
} }
@ -226,8 +264,10 @@ internal fun RelatedMemory(
galleryEntry: GalleryEntryWithMetadata, galleryEntry: GalleryEntryWithMetadata,
onSelectRelatedMemory: (GalleryId) -> Unit onSelectRelatedMemory: (GalleryId) -> Unit
) { ) {
SquareMiniature( Box(Modifier.size(130.dp).clip(RoundedCornerShape(8.dp))) {
galleryEntry.thumbnail, SquareMiniature(
false, galleryEntry.thumbnail,
onClick = { onSelectRelatedMemory(galleryEntry.id) }) false,
onClick = { onSelectRelatedMemory(galleryEntry.id) })
}
} }

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

@ -2,31 +2,30 @@ package example.imageviewer.view
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.slideInVertically import androidx.compose.animation.core.Spring
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.core.spring
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.clickable import androidx.compose.foundation.clickable
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
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
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.Picture import example.imageviewer.model.Picture
import example.imageviewer.style.ImageviewerColors.kotlinHorizontalGradientBrush
@OptIn(ExperimentalAnimationApi::class) @OptIn(ExperimentalAnimationApi::class)
@Composable @Composable
@ -41,41 +40,47 @@ internal fun PreviewImage(
image = getImage(picture) image = getImage(picture)
} }
} }
Box(Modifier.fillMaxWidth().height(393.dp), contentAlignment = Alignment.Center) {
Box(
modifier = Modifier.fillMaxSize()
.clickable { onClick() },
) {
AnimatedContent(
targetState = image,
transitionSpec = {
slideInHorizontally(
initialOffsetX = { it }, animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow
)
) with slideOutHorizontally(
targetOffsetX = { -it }, animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow
)
)
// slideInVertically(initialOffsetY = { it }) with slideOutVertically(targetOffsetY = { -it })
}
) { imageBitmap ->
if (imageBitmap != null) {
Image(
bitmap = imageBitmap,
contentDescription = null,
modifier = Modifier
.fillMaxSize(),
contentScale = ContentScale.Crop
)
} else {
Spacer(
modifier = Modifier.fillMaxSize()
Spacer( )
modifier = Modifier.height(5.dp).fillMaxWidth() }
.background(brush = kotlinHorizontalGradientBrush)
)
Card(
modifier = Modifier.height(200.dp)
.background(brush = kotlinHorizontalGradientBrush)
.padding(10.dp)
.clickable { onClick() },
shape = RoundedCornerShape(10.dp, 10.dp, 10.dp, 10.dp),
) {
AnimatedContent(
targetState = image,
transitionSpec = {
slideInVertically(initialOffsetY = { it }) with slideOutVertically(targetOffsetY = { -it })
}
) { imageBitmap ->
if (imageBitmap != null) {
Image(
bitmap = imageBitmap,
contentDescription = null,
modifier = Modifier
.fillMaxSize()
,
contentScale = ContentScale.Crop
)
} else {
Spacer(
modifier = Modifier.fillMaxSize()
.background(brush = kotlinHorizontalGradientBrush)
)
} }
} }
} }
} }
@Composable @Composable

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Loading…
Cancel
Save