Initial commit
This commit is contained in:
1
feature/details/.gitignore
vendored
Normal file
1
feature/details/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
23
feature/details/build.gradle.kts
Normal file
23
feature/details/build.gradle.kts
Normal file
@ -0,0 +1,23 @@
|
||||
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
|
||||
plugins {
|
||||
alias(libs.plugins.convention.android.library)
|
||||
alias(libs.plugins.convention.compose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "dev.adriankuta.pixabay.feature.details"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core:ui"))
|
||||
implementation(project(":data"))
|
||||
implementation(libs.androidx.ui)
|
||||
implementation(libs.androidx.ui.graphics)
|
||||
implementation(libs.androidx.ui.tooling.preview)
|
||||
implementation(libs.androidx.material3)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||
implementation(libs.androidx.hilt.navigation.compose)
|
||||
debugImplementation(libs.androidx.ui.tooling)
|
||||
debugImplementation(libs.androidx.ui.test.manifest)
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
package dev.adriankuta.pixabay.feature.details
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.ThumbUp
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.graphics.painter.ColorPainter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import coil.compose.AsyncImage
|
||||
import dev.adriankuta.pixabay.core.ui.ErrorMessage
|
||||
import dev.adriankuta.pixabay.core.ui.Loading
|
||||
import dev.adriankuta.pixabay.core.ui.R.drawable
|
||||
import dev.adriankuta.pixabay.core.ui.R.string
|
||||
import dev.adriankuta.pixabay.core.ui.StatsItem
|
||||
import dev.adriankuta.pixabay.data.model.PixabayImage
|
||||
|
||||
|
||||
@Composable
|
||||
fun PhotoDetailRoute(
|
||||
photoId: Int,
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: PhotoDetailViewModel = hiltViewModel()
|
||||
) {
|
||||
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
|
||||
LaunchedEffect(photoId) {
|
||||
viewModel.loadImage(photoId)
|
||||
}
|
||||
|
||||
PhotoDetailScreen(
|
||||
state = uiState,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun PhotoDetailScreen(
|
||||
state: PhotoDetailUiState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Scaffold { paddingValues ->
|
||||
AnimatedContent(
|
||||
state,
|
||||
label = "",
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
) { targetState ->
|
||||
when (targetState) {
|
||||
PhotoDetailUiState.Error -> ErrorMessage(
|
||||
stringResource(string.something_went_wrong),
|
||||
{},
|
||||
modifier = modifier
|
||||
)
|
||||
|
||||
PhotoDetailUiState.Loading -> Loading(modifier)
|
||||
is PhotoDetailUiState.Success -> Content(
|
||||
targetState.data,
|
||||
modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(
|
||||
data: PixabayImage,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(modifier) {
|
||||
AsyncImage(
|
||||
model = data.largeImageURL,
|
||||
contentDescription = null,
|
||||
placeholder = ColorPainter(Color.Gray),
|
||||
modifier = Modifier
|
||||
.height(260.dp)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
contentScale = ContentScale.FillHeight
|
||||
)
|
||||
Metadata(
|
||||
data,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
)
|
||||
Text(
|
||||
data.user,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
Text(
|
||||
data.tags,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Metadata(
|
||||
data: PixabayImage,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier,
|
||||
horizontalArrangement = Arrangement.SpaceAround
|
||||
) {
|
||||
StatsItem(
|
||||
icon = { Icon(Icons.Rounded.ThumbUp, null) },
|
||||
text = data.likes.toString()
|
||||
)
|
||||
StatsItem(
|
||||
icon = { Icon(painterResource(drawable.ic_download), null) },
|
||||
text = data.downloads.toString()
|
||||
)
|
||||
StatsItem(
|
||||
icon = { Icon(painterResource(drawable.ic_comment), null) },
|
||||
text = data.comments.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ContentPreview() {
|
||||
val data = PixabayImage(
|
||||
id = 5679562,
|
||||
pageURL = "https://pixabay.com/photos/berries-fruit-picking-man-fruits-5679562/",
|
||||
type = "photo",
|
||||
tags = "berries, fruit picking, man",
|
||||
previewURL = "https://cdn.pixabay.com/photo/2020/10/23/18/17/berries-5679562_150.jpg",
|
||||
previewWidth = 150,
|
||||
previewHeight = 100,
|
||||
webformatURL = "https://pixabay.com/get/g716e6d144bc71b9f94b4662cbebbb9b54f7442e6d73aab55caf2b1ed03d32f373826b2c4925431333e400248113225c4032f27de0986038211769d599350ff2e_640.jpg",
|
||||
webformatWidth = 640,
|
||||
webformatHeight = 427,
|
||||
largeImageURL = "https://pixabay.com/get/g130b59fab09e4d1a755ee1f23cdaa9aefcec7da220e0f5ac2525d95e43963b069aec3f7e19de30b435f74ca4a94d1429ed78d9218dbeff8bde43b330f68a968b_1280.jpg",
|
||||
imageWidth = 3504,
|
||||
imageHeight = 2336,
|
||||
imageSize = 1682707,
|
||||
views = 12389,
|
||||
downloads = 6681,
|
||||
likes = 48,
|
||||
comments = 11,
|
||||
user_id = 17356228,
|
||||
user = "Dave_LZ",
|
||||
userImageURL = "https://cdn.pixabay.com/user/2020/08/08/19-08-33-985_250x250.jpg"
|
||||
|
||||
)
|
||||
Content(data)
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package dev.adriankuta.pixabay.feature.details
|
||||
|
||||
import dev.adriankuta.pixabay.data.model.PixabayImage
|
||||
|
||||
sealed interface PhotoDetailUiState {
|
||||
data class Success(val data: PixabayImage) : PhotoDetailUiState
|
||||
data object Loading : PhotoDetailUiState
|
||||
data object Error : PhotoDetailUiState
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package dev.adriankuta.pixabay.feature.details
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dev.adriankuta.pixabay.data.repository.ImageRepository
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class PhotoDetailViewModel @Inject constructor(
|
||||
private val imageRepository: ImageRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val _idToLoad = MutableStateFlow<Int?>(null)
|
||||
|
||||
private val loadedData = _idToLoad
|
||||
.filterNotNull()
|
||||
.map { id ->
|
||||
imageRepository.searchImageById(id)
|
||||
}
|
||||
|
||||
val uiState = loadedData
|
||||
.map { PhotoDetailUiState.Success(it) }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
|
||||
initialValue = PhotoDetailUiState.Loading
|
||||
)
|
||||
|
||||
|
||||
fun loadImage(id: Int) {
|
||||
_idToLoad.value = id
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user