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 1 year 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 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 KotlinGradient50 = Color(0xFFC811E2)
val KotlinGradient100 = Color(0xFFE54857)
@ -39,8 +49,8 @@ internal fun ImageViewerTheme(content: @Composable () -> Unit) {
isSystemInDarkTheme() // todo check and change colors
MaterialTheme(
colorScheme = MaterialTheme.colorScheme.copy(
background = Color(0xFF1B1B1B),
onBackground = Color(0xFFFFFFFF)
background = ImageviewerColors.background,
onBackground = ImageviewerColors.onBackground
)
) {
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.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap
@ -57,7 +59,7 @@ internal fun FullscreenImage(
null
}
}
Box(Modifier.fillMaxSize().background(color = MaterialTheme.colorScheme.background)) {
Box(Modifier.fillMaxSize().background(color = ImageviewerColors.fullScreenImageBackground)) {
Column {
FullscreenImageBar(
localization,
@ -73,31 +75,52 @@ internal fun FullscreenImage(
}
})
if (imageWithFilter != null) {
val imageSize = IntSize(imageWithFilter.width, imageWithFilter.height)
val scalableState = remember(imageSize) { ScalableState(imageSize) }
val visiblePartOfImage: IntRect = scalableState.visiblePart
Slider(
modifier = Modifier.fillMaxWidth(),
value = scalableState.scale,
valueRange = MIN_SCALE..MAX_SCALE,
onValueChange = { scalableState.setScale(it) },
)
Box(
modifier = Modifier.fillMaxSize()
.onGloballyPositioned { coordinates ->
scalableState.changeBoxSize(coordinates.size)
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) {
val imageSize = IntSize(imageWithFilter.width, imageWithFilter.height)
val scalableState = remember(imageSize) { ScalableState(imageSize) }
val visiblePartOfImage: IntRect = scalableState.visiblePart
Column {
Slider(
modifier = Modifier.fillMaxWidth(),
value = scalableState.scale,
valueRange = MIN_SCALE..MAX_SCALE,
onValueChange = { scalableState.setScale(it) },
)
Box(
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 {
LoadingScreen()
@ -118,7 +141,7 @@ private fun FullscreenImageBar(
onSelectFilter: (FilterType) -> Unit
) {
TopAppBar(
modifier = Modifier.background(brush = ImageviewerColors.kotlinHorizontalGradientBrush),
modifier = Modifier.background(color = ImageviewerColors.fullScreenImageBackground),
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = ImageviewerColors.Transparent,
titleContentColor = MaterialTheme.colorScheme.onBackground
@ -128,27 +151,30 @@ private fun FullscreenImageBar(
},
navigationIcon = {
Tooltip(localization.back) {
Image(
painterResource("back.png"),
contentDescription = null,
modifier = Modifier.size(38.dp)
.clip(CircleShape)
.clickable { onBack() }
CircularButton(
painterResource("arrowleft.png"),
onClick = { 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
private fun FilterButton(
@ -165,14 +191,13 @@ private fun FilterButton(
Image(
getFilterImage(active, type = type),
contentDescription = null,
Modifier.size(38.dp)
Modifier.size(40.dp)
.hoverable(interactionSource)
.background(color = ImageviewerColors.buttonBackground(filterButtonHover))
.clickable { onClick() }
)
}
}
Spacer(Modifier.width(20.dp))
}
@OptIn(ExperimentalResourceApi::class)

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

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

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

@ -1,6 +1,8 @@
package example.imageviewer.view
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.interaction.MutableInteractionSource
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.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
@ -22,9 +25,12 @@ 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.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@ -49,38 +55,17 @@ internal fun MemoryScreen(
) {
val pictures by photoGallery.galleryStateFlow.collectAsState()
val picture = pictures.first { it.id == memoryPage.galleryId }
Column {
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() }
)
}
},
)
Box {
val scrollState = memoryPage.scrollState
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(scrollState),
.verticalScroll(scrollState)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
.height(393.dp)
.background(Color.White)
.graphicsLayer {
translationY = 0.5f * scrollState.value
@ -89,11 +74,11 @@ internal fun MemoryScreen(
) {
MemoryHeader(picture.thumbnail, onClick = { onHeaderClick(memoryPage.galleryId) })
}
Box(modifier = Modifier.background(ImageviewerColors.kotlinHorizontalGradientBrush)) {
Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Column {
Headliner("Where it happened")
Headliner("Place")
LocationVisualizer()
Headliner("What happened")
Headliner("Note")
Collapsible(
"""
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")
RelatedMemoriesVisualizer(pictures, onSelectRelatedMemory)
Spacer(Modifier.height(50.dp))
Text(
"Delete this memory",
Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
color = Color.White
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(
8.dp,
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))
}
}
}
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
private fun MemoryHeader(bitmap: ImageBitmap, onClick: () -> Unit) {
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() })) {
Image(
bitmap,
@ -129,35 +166,27 @@ private fun MemoryHeader(bitmap: ImageBitmap, onClick: () -> Unit) {
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
Column(modifier = Modifier.align(Alignment.Center)) {
Column(
modifier = Modifier.align(Alignment.BottomStart).padding(start = 12.dp, bottom = 16.dp)
) {
Text(
"Your Memory",
textAlign = TextAlign.Center,
textAlign = TextAlign.Left,
color = Color.White,
fontSize = 50.sp,
fontSize = 20.sp,
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
Text(
text,
fontSize = 12.sp,
modifier = Modifier
.padding(10.dp, 0.dp)
.clip(RoundedCornerShape(10.dp))
.background(Color.White)
.background(ImageviewerColors.noteBlockBackground)
.padding(10.dp)
.animateContentSize()
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow
)
)
.clickable(interactionSource = interctionSource, indication = null) {
isCollapsed = !isCollapsed
},
@ -185,10 +220,10 @@ internal fun Collapsible(s: String) {
internal fun Headliner(s: String) {
Text(
text = s,
fontWeight = FontWeight.Bold,
fontSize = 32.sp,
color = Color.White,
modifier = Modifier.padding(10.dp, 30.dp, 10.dp, 10.dp)
fontWeight = FontWeight.SemiBold,
fontSize = 20.sp,
color = Color.Black,
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()
.height(200.dp)
) {
LazyRow(modifier = Modifier.fillMaxSize()) {
LazyRow(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
itemsIndexed(ps) { idx, item ->
RelatedMemory(idx, item, onSelectRelatedMemory)
}
@ -226,8 +264,10 @@ internal fun RelatedMemory(
galleryEntry: GalleryEntryWithMetadata,
onSelectRelatedMemory: (GalleryId) -> Unit
) {
SquareMiniature(
galleryEntry.thumbnail,
false,
onClick = { onSelectRelatedMemory(galleryEntry.id) })
Box(Modifier.size(130.dp).clip(RoundedCornerShape(8.dp))) {
SquareMiniature(
galleryEntry.thumbnail,
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.ExperimentalAnimationApi
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.with
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
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.LaunchedEffect
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.graphics.ImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import example.imageviewer.model.Picture
import example.imageviewer.style.ImageviewerColors.kotlinHorizontalGradientBrush
@OptIn(ExperimentalAnimationApi::class)
@Composable
@ -41,41 +40,47 @@ internal fun PreviewImage(
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

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