diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/data/datasource/DetailDataSource.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/datasource/DetailDataSource.kt index b52e91d..11a3925 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/data/datasource/DetailDataSource.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/datasource/DetailDataSource.kt @@ -1,7 +1,9 @@ package com.hackathon.alddeul_babsang.data.datasource import com.hackathon.alddeul_babsang.data.dto.BaseResponse +import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangRecommendDto import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailDto +import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailRecommendDto import com.hackathon.alddeul_babsang.data.dto.response.ResponseGetReviewDto import com.hackathon.alddeul_babsang.data.dto.response.ResponseReviewDto import okhttp3.MultipartBody @@ -22,4 +24,8 @@ interface DetailDataSource { id: Int, Userid: Int ): BaseResponse + + suspend fun postRecommendStores( + storeId: Int + ): BaseResponse> } \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/data/datasourceimpl/DetailDataSourceImpl.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/datasourceimpl/DetailDataSourceImpl.kt index c950940..c66aaca 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/data/datasourceimpl/DetailDataSourceImpl.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/datasourceimpl/DetailDataSourceImpl.kt @@ -2,7 +2,9 @@ package com.hackathon.alddeul_babsang.data.datasourceimpl import com.hackathon.alddeul_babsang.data.datasource.DetailDataSource import com.hackathon.alddeul_babsang.data.dto.BaseResponse +import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangRecommendDto import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailDto +import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailRecommendDto import com.hackathon.alddeul_babsang.data.dto.response.ResponseGetReviewDto import com.hackathon.alddeul_babsang.data.dto.response.ResponseReviewDto import com.hackathon.alddeul_babsang.data.service.DetailApiService @@ -29,4 +31,9 @@ class DetailDataSourceImpl @Inject constructor( return detailApiService.postStoreDetail(id, Userid) } + override suspend fun postRecommendStores( + storeId: Int + ): BaseResponse> { + return detailApiService.postRecommendStores(storeId) + } } \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/request/RequestDetailRecommendDto.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/request/RequestDetailRecommendDto.kt new file mode 100644 index 0000000..a42bb73 --- /dev/null +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/request/RequestDetailRecommendDto.kt @@ -0,0 +1,7 @@ +package com.hackathon.alddeul_babsang.data.dto.request + +import kotlinx.serialization.SerialName + +class RequestDetailRecommendDto( + @SerialName("storeId") val storeId: Long +) \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/response/ResponseDetailRecommendDto.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/response/ResponseDetailRecommendDto.kt new file mode 100644 index 0000000..17edc94 --- /dev/null +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/response/ResponseDetailRecommendDto.kt @@ -0,0 +1,12 @@ +package com.hackathon.alddeul_babsang.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseDetailRecommendDto ( + @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/dto/response/ResponseGetReviewDto.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/response/ResponseGetReviewDto.kt index 8cdfb44..c044489 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/response/ResponseGetReviewDto.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/dto/response/ResponseGetReviewDto.kt @@ -14,6 +14,6 @@ data class Review ( @SerialName("nickname") val nickname: String, @SerialName("rate") val rate: Double, @SerialName("content") val content: String, - @SerialName("image") val imageUrl: String, + @SerialName("image") val imageUrl: String? = null, @SerialName("date") val date: String ) \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/data/repositoryimpl/DetailRepositoryImpl.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/repositoryimpl/DetailRepositoryImpl.kt index 0929f82..a3371e0 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/data/repositoryimpl/DetailRepositoryImpl.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/repositoryimpl/DetailRepositoryImpl.kt @@ -1,7 +1,9 @@ package com.hackathon.alddeul_babsang.data.repositoryimpl import com.hackathon.alddeul_babsang.data.datasource.DetailDataSource +import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangRecommendDto import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailDto +import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailRecommendDto import com.hackathon.alddeul_babsang.data.dto.response.Review import com.hackathon.alddeul_babsang.domain.repository.DetailRepository import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -53,4 +55,14 @@ class DetailRepositoryImpl @Inject constructor( detailDataSource.postStoreDetail(id = id, Userid = userId).result } } + + override suspend fun postRecommendStores( + storeId: Int + ): Result> { + return runCatching { + detailDataSource.postRecommendStores( + storeId = storeId + ).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 0ff1e26..95ffc91 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 @@ -23,4 +23,5 @@ object ApiKeyStorage { const val ID = "id" const val REVIEWS = "reviews" const val RECOMMEND = "recommend" + const val SIMILAR = "similar" } \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/data/service/DetailApiService.kt b/app/src/main/java/com/hackathon/alddeul_babsang/data/service/DetailApiService.kt index c262912..5f1a07c 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/data/service/DetailApiService.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/data/service/DetailApiService.kt @@ -1,12 +1,16 @@ package com.hackathon.alddeul_babsang.data.service import com.hackathon.alddeul_babsang.data.dto.BaseResponse +import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangRecommendDto import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailDto +import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailRecommendDto import com.hackathon.alddeul_babsang.data.dto.response.ResponseGetReviewDto import com.hackathon.alddeul_babsang.data.dto.response.ResponseReviewDto import com.sopt.data.service.ApiKeyStorage.ID +import com.sopt.data.service.ApiKeyStorage.RECOMMEND import com.sopt.data.service.ApiKeyStorage.REVIEW import com.sopt.data.service.ApiKeyStorage.REVIEWS +import com.sopt.data.service.ApiKeyStorage.SIMILAR import com.sopt.data.service.ApiKeyStorage.STORES import com.sopt.data.service.ApiKeyStorage.STORE_ID import okhttp3.MultipartBody @@ -39,4 +43,8 @@ interface DetailApiService { @Query("Userid") Userid: Int ): BaseResponse + @POST("/$RECOMMEND/$SIMILAR") + suspend fun postRecommendStores( + @Query("storeId") storeId: Int + ) : BaseResponse> } \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/domain/repository/DetailRepository.kt b/app/src/main/java/com/hackathon/alddeul_babsang/domain/repository/DetailRepository.kt index 6ebf282..eb944e3 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/domain/repository/DetailRepository.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/domain/repository/DetailRepository.kt @@ -1,6 +1,8 @@ package com.hackathon.alddeul_babsang.domain.repository +import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangRecommendDto import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailDto +import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailRecommendDto import com.hackathon.alddeul_babsang.data.dto.response.Review import java.io.File @@ -21,4 +23,8 @@ interface DetailRepository { id: Int, userId: Int ): Result + + suspend fun postRecommendStores( + storeId: Int + ): Result> } \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/DetailRecommendedItem.kt b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/DetailRecommendedItem.kt index e33fff6..15f26e7 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/DetailRecommendedItem.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/DetailRecommendedItem.kt @@ -1,8 +1,11 @@ package com.hackathon.alddeul_babsang.presentation.detail.screen +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.Column +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -13,6 +16,7 @@ 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.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil.compose.AsyncImage @@ -26,41 +30,20 @@ import com.hackathon.alddeul_babsang.core_ui.theme.Pink import com.hackathon.alddeul_babsang.core_ui.theme.Yellow import com.hackathon.alddeul_babsang.core_ui.theme.body1Semi import com.hackathon.alddeul_babsang.core_ui.theme.body4Regular +import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailRecommendDto import com.hackathon.alddeul_babsang.domain.entity.BabsangRecommendEntity +import com.hackathon.alddeul_babsang.presentation.babsang.screen.LoadImage @Composable fun DetailRecommendedItem( - data: BabsangRecommendEntity, + data: ResponseDetailRecommendDto, onClick: () -> Unit = {} ) { Column( modifier = Modifier.clickable { onClick() } ) { - AsyncImage( - model = data.avatar ?: when (data.codeName) { - "한식" -> R.drawable.ic_korean_food - "중식" -> R.drawable.ic_chinese_food - "경양식/일식" -> R.drawable.ic_japanese_food - else -> R.drawable.ic_etc_food - }, - contentDescription = null, - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(10.dp)) - .height(200.dp) - .background( - color = when (data.codeName) { - "한식" -> Orange700 - "중식" -> Yellow - "경양식/일식" -> Pink - "기타외식업" -> Blue - else -> Font_B04 - }, - shape = RoundedCornerShape(14.dp) - ), - contentScale = if (data.avatar == null) ContentScale.None else ContentScale.FillBounds, - alignment = Alignment.Center - ) + LoadImage2(data.category) + Text( modifier = Modifier.padding(vertical = 5.dp), text = data.name, @@ -68,24 +51,61 @@ fun DetailRecommendedItem( color = Black ) Text( - text = data.codeName, + text = data.category, style = body4Regular, color = Font_B04 ) } } +@Composable +fun LoadImage2(codeName: String) { + val imageId = when (codeName) { + "WESTERN_JAPANESE" -> R.drawable.ic_japanese_food + "KOREAN" -> R.drawable.ic_korean_food + "CHINESE" -> R.drawable.ic_chinese_food + else -> R.drawable.ic_etc_food + } + val backgroundColor = when (codeName) { + "WESTERN_JAPANESE" -> Yellow + "KOREAN" -> Orange700 + "CHINESE" -> Pink + else -> Blue + } + + Box( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(10.dp)) + .height(200.dp) + .background( + color = backgroundColor, + shape = RoundedCornerShape(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) + ) + } +} + @Preview(showBackground = true) @Composable fun DetailRecommendedItemPreview() { AlddeulBabsangTheme { DetailRecommendedItem( - data = BabsangRecommendEntity( - id = 1, - avatar = null, + data = ResponseDetailRecommendDto( + storeId = 1, name = "족발 야시장", - codeName = "한식", - address = "용산 동자동", + category = "한식", + region = "용산구", ) ) } diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/DetailScreen.kt b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/DetailScreen.kt index 8ea2272..365ee73 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/DetailScreen.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/DetailScreen.kt @@ -16,7 +16,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button @@ -49,17 +49,19 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil.compose.AsyncImage import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.hackathon.alddeul_babsang.R +import com.hackathon.alddeul_babsang.core_ui.component.LoadingCircleIndicator import com.hackathon.alddeul_babsang.core_ui.theme.Blue import com.hackathon.alddeul_babsang.core_ui.theme.Gray200 import com.hackathon.alddeul_babsang.core_ui.theme.Gray300 -import com.hackathon.alddeul_babsang.core_ui.theme.Gray500 import com.hackathon.alddeul_babsang.core_ui.theme.Gray900 import com.hackathon.alddeul_babsang.core_ui.theme.Orange400 import com.hackathon.alddeul_babsang.core_ui.theme.Orange700 +import com.hackathon.alddeul_babsang.core_ui.theme.Orange900 import com.hackathon.alddeul_babsang.core_ui.theme.Red import com.hackathon.alddeul_babsang.core_ui.theme.White import com.hackathon.alddeul_babsang.core_ui.theme.body4Semi import com.hackathon.alddeul_babsang.core_ui.theme.head4Bold +import com.hackathon.alddeul_babsang.core_ui.theme.head6Semi import com.hackathon.alddeul_babsang.core_ui.theme.head7Regular import com.hackathon.alddeul_babsang.core_ui.theme.head7Semi import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailDto @@ -87,6 +89,7 @@ fun DetailRoute( LaunchedEffect(Unit) { detailViewModel.getReviews(id) detailViewModel.postDetail(id.toInt()) + detailViewModel.postDetailRecommend(id.toInt()) } when (postDetailState) { @@ -125,6 +128,10 @@ fun DetailScreen( val getReviewsState by detailViewModel.getReviewsState.collectAsStateWithLifecycle(UiState.Empty) val likeViewModel: LikeViewModel = hiltViewModel() + val postDetailRecommendState by detailViewModel.postDetailRecommendState.collectAsStateWithLifecycle( + UiState.Empty + ) + Scaffold( modifier = Modifier.fillMaxSize(), topBar = { @@ -345,24 +352,57 @@ fun DetailScreen( color = Gray900 ) } - item { - LazyVerticalGrid( - contentPadding = PaddingValues(horizontal = 20.dp), - columns = GridCells.Fixed(2), - modifier = Modifier - .fillMaxWidth() - .height(550.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - items(detailViewModel.mockDetailRecommend) { item -> - DetailRecommendedItem( - data = item, - onClick = { onItemClick(item.id) } + + + when (postDetailRecommendState) { + is UiState.Loading -> { + item { + LoadingCircleIndicator() + } + } + + is UiState.Success -> { + val data = (postDetailRecommendState as UiState.Success).data + if (data.isEmpty()) { + } else { + item { + LazyVerticalGrid( + contentPadding = PaddingValues(horizontal = 20.dp), + columns = GridCells.Fixed(2), + modifier = Modifier + .fillMaxWidth() + .height(550.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + itemsIndexed(data) { index, item -> + DetailRecommendedItem( + onClick = { onItemClick(item.storeId) }, + data = item + ) + } + } + } + } + + + } + + is UiState.Failure -> { + item { + Text( + text = (postDetailRecommendState as UiState.Failure).msg, + style = head6Semi, + color = Orange900, + modifier = Modifier.padding(vertical = 20.dp) ) } } + + else -> {} + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/DetailViewModel.kt b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/DetailViewModel.kt index cce6dfa..2852a0b 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/DetailViewModel.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/DetailViewModel.kt @@ -2,7 +2,9 @@ package com.hackathon.alddeul_babsang.presentation.detail.screen import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.hackathon.alddeul_babsang.data.dto.response.ResponseBabsangRecommendDto import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailDto +import com.hackathon.alddeul_babsang.data.dto.response.ResponseDetailRecommendDto import com.hackathon.alddeul_babsang.data.dto.response.Review import com.hackathon.alddeul_babsang.domain.entity.BabsangDetailEntity import com.hackathon.alddeul_babsang.domain.entity.BabsangRecommendEntity @@ -16,6 +18,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel @@ -28,6 +31,10 @@ class DetailViewModel @Inject constructor( private val _postDetailState = MutableSharedFlow>(replay = 1) val postDetailState: SharedFlow> = _postDetailState + private val _postDetailRecommendState = + MutableStateFlow>>(UiState.Empty) + val postDetailRecommendState: StateFlow>> = _postDetailRecommendState + fun getReviews(id: Long) = viewModelScope.launch { _getReviewsState.emit(UiState.Loading) detailRepository.getReviews(id).fold( @@ -52,6 +59,23 @@ class DetailViewModel @Inject constructor( ) } + + fun postDetailRecommend(storeId: Int) = viewModelScope.launch { + _postDetailRecommendState.emit(UiState.Loading) + detailRepository.postRecommendStores(storeId = storeId).fold( + onSuccess = { + _postDetailRecommendState.emit(UiState.Success(it)) + }, + onFailure = { + _postDetailRecommendState.emit(UiState.Failure(it.message.toString())) + Timber.e(it.localizedMessage) + } + ) + } + + + + val mockMenuList = listOf( MenuEntity("김치찌개", 8000), MenuEntity("된장찌개", 9000), diff --git a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/ReviewScreen.kt b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/ReviewScreen.kt index 2f0a871..106570b 100644 --- a/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/ReviewScreen.kt +++ b/app/src/main/java/com/hackathon/alddeul_babsang/presentation/detail/screen/ReviewScreen.kt @@ -73,11 +73,17 @@ fun ReviewRoute( val systemUiController = rememberSystemUiController() val postReviewState by reviewViewModel.postReviewState.collectAsStateWithLifecycle(UiState.Empty) + // navigateBack이 이미 호출되었는지 여부를 추적하는 상태 변수 + val hasNavigatedBack = remember { mutableStateOf(false) } + when (postReviewState) { is UiState.Success -> { - keyboardController?.hide() - navigator.navigateBack() - Timber.d("Review post success") + if (!hasNavigatedBack.value) { + keyboardController?.hide() + navigator.navigateBack() // 한 번만 호출되도록 처리 + hasNavigatedBack.value = true // 한 번 호출된 후에는 다시 호출되지 않도록 설정 + Timber.d("Review post success") + } } is UiState.Failure -> { @@ -97,7 +103,6 @@ fun ReviewRoute( ReviewScreen( id = id, - onBackClick = { navigator.navigateBack() }, reviewViewModel = reviewViewModel ) }