Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task/union type image resource video preview #136

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions core/design/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ dependencies {

val composeBom = platform(libs.compose.bom)
implementation(composeBom)
implementation(libs.bundles.compose)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.core.ktx)
implementation(libs.bundles.compose)
implementation(libs.coil.compose)

androidTestImplementation(libs.bundles.compose.androidTest)
androidTestImplementation(composeBom)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable
* This is useful to abstract the image resource type and use it in a composable function. The identifier can be used for tests.
* @see ImageResource.Vector
* @see ImageResource.Drawable
* @see ImageResourceIcon
* @see ImageResourceComposable
*/
@Immutable
sealed class ImageResource {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package edu.stanford.spezi.core.design.component

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ThumbUp
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import coil.compose.AsyncImagePainter
import coil.compose.SubcomposeAsyncImage
import edu.stanford.spezi.core.design.theme.Colors
import edu.stanford.spezi.core.design.theme.SpeziTheme
import edu.stanford.spezi.core.design.theme.ThemePreviews
import edu.stanford.spezi.core.utils.extensions.imageResourceIdentifier

/**
* Composable function to display an icon using an [ImageResource].
*/
@Composable
fun ImageResourceComposable(
imageResource: ImageResource,
contentDescription: String,
modifier: Modifier = Modifier,
tint: Color = Colors.primary,
) {
val imageModifier = modifier.then(Modifier.imageResourceIdentifier(imageResource.identifier))
when (imageResource) {

Check warning on line 37 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L36-L37

Added lines #L36 - L37 were not covered by tests
is ImageResource.Vector -> {
Icon(
imageVector = imageResource.image,
contentDescription = contentDescription,
tint = tint,
modifier = imageModifier

Check warning on line 43 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L39-L43

Added lines #L39 - L43 were not covered by tests
)
}

is ImageResource.Drawable -> {
Icon(
painter = painterResource(id = imageResource.resId),
contentDescription = contentDescription,
tint = tint,
modifier = imageModifier,

Check warning on line 52 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L48-L52

Added lines #L48 - L52 were not covered by tests
)
}

is ImageResource.Remote -> {
SubcomposeAsyncImage(
model = imageResource.url,
modifier = modifier,
contentDescription = contentDescription,
) {

Check warning on line 61 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L57-L61

Added lines #L57 - L61 were not covered by tests
val state = painter.state
val painter = painter

Check warning on line 63 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L63

Added line #L63 was not covered by tests
if (state is AsyncImagePainter.State.Loading) {
Box(Modifier.matchParentSize()) {
CircularProgressIndicator(
Modifier
.align(Alignment.Center),
color = Colors.primary

Check warning on line 69 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L65-L69

Added lines #L65 - L69 were not covered by tests
)
}

Check warning on line 71 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L71

Added line #L71 was not covered by tests
}

if (state is AsyncImagePainter.State.Error) {
Box(Modifier.matchParentSize()) {
Text("Error loading image", Modifier.align(Alignment.Center))
}

Check warning on line 77 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L75-L77

Added lines #L75 - L77 were not covered by tests
}

if (state is AsyncImagePainter.State.Success) {
Box(
modifier = Modifier.matchParentSize(),
contentAlignment = Alignment.Center

Check warning on line 83 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L81-L83

Added lines #L81 - L83 were not covered by tests
) {
Image(
modifier = Modifier.fillMaxSize(),
painter = painter,
contentDescription = contentDescription,
contentScale = ContentScale.Crop,

Check warning on line 89 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L85-L89

Added lines #L85 - L89 were not covered by tests
)
}
}
}

Check warning on line 93 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L91-L93

Added lines #L91 - L93 were not covered by tests
}
}
}

@ThemePreviews
@Composable
private fun ImageResourceComposablePreview(
@PreviewParameter(ImageResourceProvider::class) imageResource: ImageResource,
) {
SpeziTheme(isPreview = true) {

Check warning on line 103 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L103

Added line #L103 was not covered by tests
ImageResourceComposable(
imageResource = imageResource,
contentDescription = "Icon"
)
}

Check warning on line 108 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L105-L108

Added lines #L105 - L108 were not covered by tests
}

private class ImageResourceProvider : PreviewParameterProvider<ImageResource> {
override val values: Sequence<ImageResource> = sequenceOf(
ImageResource.Vector(Icons.Default.ThumbUp),
ImageResource.Drawable(android.R.drawable.ic_menu_camera),

Check warning on line 114 in core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt

View check run for this annotation

Codecov / codecov/patch

core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/ImageResourceComposable.kt#L111-L114

Added lines #L111 - L114 were not covered by tests
)
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import edu.stanford.spezi.core.design.component.ImageResource
import edu.stanford.spezi.core.design.component.ImageResourceIcon
import edu.stanford.spezi.core.design.component.ImageResourceComposable
import edu.stanford.spezi.core.design.component.StringResource
import edu.stanford.spezi.core.design.theme.Sizes
import edu.stanford.spezi.core.design.theme.Spacings
Expand Down Expand Up @@ -77,7 +77,7 @@ fun ContactComposable(contact: Contact, modifier: Modifier = Modifier) {
verticalAlignment = Alignment.CenterVertically
) {
val image = contact.image ?: ImageResource.Vector(Icons.Default.AccountBox)
ImageResourceIcon(
ImageResourceComposable(
imageResource = image,
contentDescription = stringResource(R.string.profile_picture),
modifier = Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package edu.stanford.spezi.modules.education.videos.component
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
Expand All @@ -13,7 +12,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
Expand All @@ -24,7 +22,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
Expand All @@ -35,13 +32,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImagePainter
import coil.compose.SubcomposeAsyncImage
import edu.stanford.spezi.core.design.component.DefaultElevatedCard
import edu.stanford.spezi.core.design.component.ImageResource
import edu.stanford.spezi.core.design.component.ImageResourceComposable
import edu.stanford.spezi.core.design.component.RectangleShimmerEffect
import edu.stanford.spezi.core.design.component.VerticalSpacer
import edu.stanford.spezi.core.design.component.height
Expand Down Expand Up @@ -154,74 +150,51 @@ private fun VideoItem(video: Video, onVideoClick: () -> Unit) {

VerticalSpacer(height = Spacings.small)

SubcomposeAsyncImage(
Box(
modifier = Modifier
.clickable { onVideoClick() }
.height(IMAGE_HEIGHT.dp)
.padding(Spacings.small)
.fillMaxWidth(),
model = video.thumbnailUrl,

contentDescription = "Video thumbnail",
.fillMaxWidth()
) {
val state = painter.state
val painter = painter
if (state is AsyncImagePainter.State.Loading) {
Box(Modifier.matchParentSize()) {
CircularProgressIndicator(
Modifier
.align(Alignment.Center)
)
}
}

if (state is AsyncImagePainter.State.Error) {
Box(Modifier.matchParentSize()) {
Text("Error loading image", Modifier.align(Alignment.Center))
}
}
ImageResourceComposable(
imageResource = ImageResource.Remote(video.thumbnailUrl),
contentDescription = "Video thumbnail",
modifier = Modifier
.fillMaxWidth()
.aspectRatio(ASPECT_16_9)
.border(Sizes.Border.medium, Colors.primary),
)

if (state is AsyncImagePainter.State.Success) {
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(ASPECT_16_9)
.border(Sizes.Border.medium, Colors.primary),
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier.fillMaxSize(),
painter = painter,
contentDescription = "Video thumbnail",
contentScale = ContentScale.Crop,
Box(
modifier = Modifier
.align(Alignment.Center)
.background(
color = Colors.primary,
shape = CircleShape
)
Box(
modifier = Modifier
.align(Alignment.Center)
.background(
color = Colors.primary,
shape = CircleShape
)
.padding(Spacings.medium)
) {
Icon(
imageVector = Icons.Default.PlayArrow,
contentDescription = "Play button",
modifier = Modifier
.align(Alignment.Center)
.size(Sizes.Icon.medium),
tint = Colors.onPrimary
)
}
}
.padding(Spacings.medium)
) {
Icon(
imageVector = Icons.Default.PlayArrow,
contentDescription = "Play button",
modifier = Modifier
.align(Alignment.Center)
.size(Sizes.Icon.medium),
tint = Colors.onPrimary
)
}
}
}
}

@Composable
fun LoadingVideoCard() {
VideoElevatedCard(modifier = Modifier.fillMaxWidth().padding(bottom = Spacings.medium)) {
VideoElevatedCard(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = Spacings.medium)
) {
Column(
modifier = Modifier.padding(Spacings.medium),
verticalArrangement = Arrangement.spacedBy(Spacings.large + Spacings.medium)
Expand Down
Loading