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