From 47efc2a5bb411492b25d906c2ac10b3221d616d9 Mon Sep 17 00:00:00 2001 From: seoyeonsw Date: Sat, 9 Nov 2024 04:18:23 +0900 Subject: [PATCH] =?UTF-8?q?#26=20[FEAT]=20=EB=B0=A5=EC=83=81=20=EC=B6=94?= =?UTF-8?q?=EC=B2=9C=20=EB=AA=A8=EB=8D=B8=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/BabsangDataSource.kt | 5 + .../datasourceimpl/BabsangDataSourceImpl.kt | 7 ++ .../dto/request/RequestBabsangRecommendDto.kt | 7 ++ .../response/ResponseBabsangRecommendDto.kt | 12 +++ .../repositoryimpl/BabsangRepositoryImpl.kt | 11 +++ .../data/service/ApiKeyStorage.kt | 1 + .../data/service/BabsangApiService.kt | 7 ++ .../domain/repository/BabsangRepository.kt | 5 + .../babsang/screen/BabsangRecommendItem.kt | 66 +++++-------- .../babsang/screen/BabsangScreen.kt | 95 ++++++++++++++----- .../babsang/screen/BabsangViewModel.kt | 19 ++++ 11 files changed, 170 insertions(+), 65 deletions(-) create mode 100644 app/src/main/java/com/hackathon/alddeul_babsang/data/dto/request/RequestBabsangRecommendDto.kt create mode 100644 app/src/main/java/com/hackathon/alddeul_babsang/data/dto/response/ResponseBabsangRecommendDto.kt diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/data/datasource/BabsangDataSource.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/datasource/BabsangDataSource.kt index 76a95ce..bfdbc1b 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/data/datasource/BabsangDataSource.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/datasource/BabsangDataSource.kt @@ -2,9 +2,14 @@ package com.hackathon.alddeul_babsang.data.datasource import com.hackathon.alddeul_babsang.data.dto.BaseResponse import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangDto +import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangRecommendDto interface BabsangDataSource { suspend fun postStores( userId: Int ): BaseResponse> + + suspend fun postRecommendStores( + userId: Int + ): BaseResponse> } \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/data/datasourceimpl/BabsangDataSourceImpl.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/datasourceimpl/BabsangDataSourceImpl.kt index 986e6eb..9ad0d30 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/data/datasourceimpl/BabsangDataSourceImpl.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/datasourceimpl/BabsangDataSourceImpl.kt @@ -3,6 +3,7 @@ package com.hackathon.alddeul_babsang.data.datasourceimpl import com.hackathon.alddeul_babsang.data.datasource.BabsangDataSource import com.hackathon.alddeul_babsang.data.dto.BaseResponse import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangDto +import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangRecommendDto import com.hackathon.alddeul_babsang.data.service.BabsangApiService import javax.inject.Inject @@ -14,4 +15,10 @@ class BabsangDataSourceImpl @Inject constructor( ): BaseResponse> { return babsangApiService.postStores(userId) } + + override suspend fun postRecommendStores( + userId: Int + ): BaseResponse> { + return babsangApiService.postRecommendStores(userId) + } } \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/request/RequestBabsangRecommendDto.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/request/RequestBabsangRecommendDto.kt new file mode 100644 index 0000000..38301a7 --- /dev/null +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/request/RequestBabsangRecommendDto.kt @@ -0,0 +1,7 @@ +package com.hackathon.alddeul_babsang.data.dto.request + +import kotlinx.serialization.SerialName + +class RequestBabsangRecommendDto( + @SerialName("userId") val userId: Long +) \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/response/ResponseBabsangRecommendDto.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/response/ResponseBabsangRecommendDto.kt new file mode 100644 index 0000000..fbda4dc --- /dev/null +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/response/ResponseBabsangRecommendDto.kt @@ -0,0 +1,12 @@ +package com.hackathon.alddeul_babsang.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseBabsangRecommendDto ( + @SerialName("name") val name: String, + @SerialName("category") val category: String, + @SerialName("region") val region: String, + @SerialName("storeId") val storeId: Long, +) \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/data/repositoryimpl/BabsangRepositoryImpl.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/repositoryimpl/BabsangRepositoryImpl.kt index dc3ea1a..49e6e7f 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/data/repositoryimpl/BabsangRepositoryImpl.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/repositoryimpl/BabsangRepositoryImpl.kt @@ -2,6 +2,7 @@ package com.hackathon.alddeul_babsang.data.repositoryimpl import com.hackathon.alddeul_babsang.data.datasource.BabsangDataSource import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangDto +import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangRecommendDto import com.hackathon.alddeul_babsang.domain.repository.BabsangRepository import javax.inject.Inject @@ -17,4 +18,14 @@ class BabsangRepositoryImpl @Inject constructor( ).result ?: emptyList() } } + + override suspend fun postRecommendStores( + userId: Int + ): Result> { + return runCatching { + babsangDataSource.postRecommendStores( + userId = userId + ).result ?: emptyList() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/data/service/ApiKeyStorage.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/service/ApiKeyStorage.kt index 575bd6b..0ff1e26 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/data/service/ApiKeyStorage.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/service/ApiKeyStorage.kt @@ -22,4 +22,5 @@ object ApiKeyStorage { const val STORE_ID = "storeId" const val ID = "id" const val REVIEWS = "reviews" + const val RECOMMEND = "recommend" } \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/data/service/BabsangApiService.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/service/BabsangApiService.kt index ba99b64..a0059fb 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/data/service/BabsangApiService.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/service/BabsangApiService.kt @@ -2,6 +2,8 @@ package com.hackathon.alddeul_babsang.data.service import com.hackathon.alddeul_babsang.data.dto.BaseResponse import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangDto +import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangRecommendDto +import com.sopt.data.service.ApiKeyStorage.RECOMMEND import com.sopt.data.service.ApiKeyStorage.STORES import retrofit2.http.POST import retrofit2.http.Query @@ -11,4 +13,9 @@ interface BabsangApiService { suspend fun postStores( @Query("userId") userId: Int ): BaseResponse> + + @POST("/$RECOMMEND") + suspend fun postRecommendStores( + @Query("userId") userId: Int + ) : BaseResponse> } \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/domain/repository/BabsangRepository.kt b/app/src/main/java/com/hackathon/alddeul_babsang/domain/repository/BabsangRepository.kt index a2505cc..0ab3381 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/domain/repository/BabsangRepository.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/domain/repository/BabsangRepository.kt @@ -1,9 +1,14 @@ package com.hackathon.alddeul_babsang.domain.repository import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangDto +import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangRecommendDto interface BabsangRepository { suspend fun postStores( userId: Int ): Result> + + suspend fun postRecommendStores( + userId: Int + ): Result> } \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/babsang/screen/BabsangRecommendItem.kt b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/babsang/screen/BabsangRecommendItem.kt index 5d79c7e..d6d6e17 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/babsang/screen/BabsangRecommendItem.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/babsang/screen/BabsangRecommendItem.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxHeight 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.shape.RoundedCornerShape import androidx.compose.material3.Text @@ -19,13 +18,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage import com.hackathon.alddeul_babsang.R import com.hackathon.alddeul_babsang.core_ui.theme.AlddeulBabsangTheme import com.hackathon.alddeul_babsang.core_ui.theme.Blue @@ -41,12 +38,12 @@ import com.hackathon.alddeul_babsang.core_ui.theme.body4Regular import com.hackathon.alddeul_babsang.core_ui.theme.body7Semi import com.hackathon.alddeul_babsang.core_ui.theme.head4Bold import com.hackathon.alddeul_babsang.core_ui.theme.head5Bold -import com.hackathon.alddeul_babsang.domain.entity.BabsangRecommendEntity +import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangRecommendDto @Composable fun BabsangRecommendItem( onClick: () -> Unit = {}, - data: BabsangRecommendEntity + data: ResponseBabsangRecommendDto ) { Column( modifier = Modifier @@ -60,8 +57,8 @@ fun BabsangRecommendItem( .fillMaxHeight() .clip(RoundedCornerShape(14.dp)) ) { - // AsyncImage 로드 - ReplaceImage(data.codeName, data.avatar) + //카테고리별 이미지 지정 + LoadImage(data.category) Column( modifier = Modifier .padding(top = 110.dp) @@ -75,7 +72,7 @@ fun BabsangRecommendItem( .background(White), contentAlignment = Alignment.Center ) { - Text(data.address, textAlign = TextAlign.Center, style = body7Semi) + Text(data.region, textAlign = TextAlign.Center, style = body7Semi) } Text( modifier = Modifier @@ -89,7 +86,7 @@ fun BabsangRecommendItem( ) Text( modifier = Modifier.padding(top = 6.dp), - text = data.codeName, + text = data.category, style = body4Regular, color = White ) @@ -105,7 +102,7 @@ fun BabsangRecommendItem( ) Spacer(modifier = Modifier.width(15.dp)) Text( - text = data.codeName, + text = data.category, style = body2Regular, color = Orange800, modifier = Modifier @@ -115,7 +112,7 @@ fun BabsangRecommendItem( } Spacer(modifier = Modifier.height(12.dp)) Text( - text = data.address, + text = data.region, style = body4Regular, color = Gray300, modifier = Modifier.padding(start = 20.dp) @@ -126,7 +123,7 @@ fun BabsangRecommendItem( } @Composable -fun ReplaceImage(codeName: String, imageUrl: String?) { +fun LoadImage(codeName: String) { val imageId = when (codeName) { "경양식/일식" -> R.drawable.ic_japanese_food "한식" -> R.drawable.ic_korean_food @@ -146,32 +143,16 @@ fun ReplaceImage(codeName: String, imageUrl: String?) { .fillMaxHeight() .background(color = backgroundColor) // 배경색 설정 ) { - if (imageUrl.isNullOrEmpty()) { - // imageUrl이 null이나 empty일 경우 대체 이미지 표시 - Image( - painter = painterResource(id = imageId), // 대체 이미지 - contentDescription = null, - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .padding(horizontal = 15.dp) - .padding(bottom = 80.dp) - .padding(top = 15.dp) - ) - } else { - // imageUrl이 null이 아니면 AsyncImage로 비동기 이미지 로드 - AsyncImage( - model = imageUrl, - contentDescription = null, - placeholder = painterResource(id = imageId), - contentScale = ContentScale.Crop, // 이미지 비율 유지, 크기 확대 또는 자르기 - modifier = Modifier - .fillMaxWidth() // 가로는 꽉 차게 - .fillMaxHeight() // 세로도 꽉 차게 - .clip(RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp)) - ) - - } + Image( + painter = painterResource(id = imageId), // 대체 이미지 + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .padding(horizontal = 15.dp) + .padding(bottom = 80.dp) + .padding(top = 15.dp) + ) } } @@ -180,12 +161,11 @@ fun ReplaceImage(codeName: String, imageUrl: String?) { fun BabsangRecommendItemPreview() { AlddeulBabsangTheme { BabsangRecommendItem( - data = BabsangRecommendEntity( - id = 1, - avatar = null, + data = ResponseBabsangRecommendDto( + storeId = 1, name = "송이네 밥상", - codeName = "경양식/일식", - address = "용산구", + category = "한식", + region = "용산구", ) ) } diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/babsang/screen/BabsangScreen.kt b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/babsang/screen/BabsangScreen.kt index 9504dd9..3708216 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/babsang/screen/BabsangScreen.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/babsang/screen/BabsangScreen.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api @@ -51,6 +50,7 @@ fun BabsangRoute( LaunchedEffect(Unit) { babsangViewModel.postBabsang() + babsangViewModel.postBabsangRecommend() } SideEffect { @@ -72,6 +72,10 @@ fun BabsangScreen( babsangViewModel: BabsangViewModel, ) { val getBabsangState by babsangViewModel.postBabsangState.collectAsStateWithLifecycle(UiState.Empty) + val postBabsangRecommendState by babsangViewModel.postBabsangRecommendState.collectAsStateWithLifecycle( + UiState.Empty + ) + Scaffold( topBar = { @@ -97,38 +101,85 @@ fun BabsangScreen( .background(color = White) .padding(horizontal = 20.dp) ) { - item { - Text( - modifier = Modifier.padding(bottom = 15.dp), - text = buildAnnotatedString { - withStyle(style = SpanStyle(color = Orange800)) { - append(stringResource(R.string.tv_babsang_recommend1)) + when (postBabsangRecommendState) { + is UiState.Loading -> { + item { + LoadingCircleIndicator() + } + } + + is UiState.Success -> { + val data = (postBabsangRecommendState as UiState.Success).data + if (data.isEmpty()) { + item { + Text( + text = "아직 좋아요를 누른 밥상이 없어요", + style = head6Semi, + color = Orange900, + modifier = Modifier.padding(vertical = 20.dp) + ) } - append(stringResource(R.string.tv_babsang_recommend2)) - withStyle(style = SpanStyle(color = Orange800)) { - append(stringResource(R.string.tv_babsang_recommend3)) + } else { + item { + Text( + modifier = Modifier.padding(bottom = 15.dp), + text = buildAnnotatedString { + withStyle(style = SpanStyle(color = Orange800)) { + append(stringResource(R.string.tv_babsang_recommend1)) + } + append(stringResource(R.string.tv_babsang_recommend2)) + withStyle(style = SpanStyle(color = Orange800)) { + append(stringResource(R.string.tv_babsang_recommend3)) + } + append(stringResource(R.string.tv_babsang_recommend4)) + }, + style = head6Semi + ) + + LazyRow( + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.fillMaxWidth() + ) { + itemsIndexed(data) { index, item -> + BabsangRecommendItem( + onClick = { onItemClick(item.storeId) }, + data = item + ) + if (index != data.size - 1) { + Spacer(modifier = Modifier.height(16.dp)) + } + } + } + } - append(stringResource(R.string.tv_babsang_recommend4)) - }, - style = head6Semi - ) - LazyRow( - horizontalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.fillMaxWidth() - ) { - items(babsangViewModel.mockBabsangRecommendList) { item -> - BabsangRecommendItem( - onClick = { onItemClick(item.id) }, - data = item + } + + + } + + is UiState.Failure -> { + item { + Text( + text = (getBabsangState as UiState.Failure).msg, + style = head6Semi, + color = Orange900, + modifier = Modifier.padding(vertical = 20.dp) ) } } + + else -> {} + + } + + item { Text( modifier = Modifier.padding(top = 30.dp, bottom = 15.dp), text = stringResource(R.string.tv_babsang_list), style = head6Semi ) } + when (getBabsangState) { is UiState.Loading -> { item { diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/babsang/screen/BabsangViewModel.kt b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/babsang/screen/BabsangViewModel.kt index 6d4b674..c6982c0 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/babsang/screen/BabsangViewModel.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/babsang/screen/BabsangViewModel.kt @@ -3,6 +3,7 @@ package com.hackathon.alddeul_babsang.presentation.babsang.screen import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangDto +import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangRecommendDto import com.hackathon.alddeul_babsang.domain.entity.BabsangRecommendEntity import com.hackathon.alddeul_babsang.domain.repository.BabsangRepository import com.hackathon.alddeul_babsang.util.UiState @@ -21,6 +22,11 @@ class BabsangViewModel @Inject constructor( MutableStateFlow>>(UiState.Empty) val postBabsangState: StateFlow>> = _postBabsangState + private val _postBabsangRecommendState = + MutableStateFlow>>(UiState.Empty) + val postBabsangRecommendState: StateFlow>> = _postBabsangRecommendState + + fun postBabsang() = viewModelScope.launch { _postBabsangState.emit(UiState.Loading) babsangRepository.postStores(userId = 1).fold( @@ -34,6 +40,19 @@ class BabsangViewModel @Inject constructor( ) } + fun postBabsangRecommend() = viewModelScope.launch { + _postBabsangRecommendState.emit(UiState.Loading) + babsangRepository.postRecommendStores(userId = 1).fold( + onSuccess = { + _postBabsangRecommendState.emit(UiState.Success(it)) + }, + onFailure = { + _postBabsangRecommendState.emit(UiState.Failure(it.message.toString())) + Timber.e(it.localizedMessage) + } + ) + } + val mockBabsangRecommendList = listOf( BabsangRecommendEntity( id = 1,