From 64d4fcd7324abd572dca23410211619055c33259 Mon Sep 17 00:00:00 2001 From: KwakEuiJin Date: Fri, 16 Feb 2024 22:41:55 +0900 Subject: [PATCH] =?UTF-8?q?[feat/login]:=20=EC=8B=9D=EB=8B=B9=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=ED=99=94=EB=A9=B4=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/review/PostReviewUseCase.kt | 5 +- presentation/build.gradle.kts | 1 + .../presentation/model/Restaurant.kt | 19 +++ .../ui/bottom/BottomNavigation.kt | 3 +- .../presentation/ui/main/MainScreen.kt | 34 ++++- .../presentation/ui/review/ReviewScreen.kt | 144 ++++++++---------- .../ui/review/ReviewScreenContract.kt | 48 ------ .../ui/review/ReviewScreenViewModel.kt | 58 ------- .../presentation/ui/review/ReviewViewModel.kt | 137 +++++++++++++++++ .../ui/review/write/ReviewWriteScreen.kt | 91 +++++++++-- .../presentation/ui/search/SearchScreen.kt | 12 +- .../presentation/util/ViewModelExtension.kt | 21 +++ presentation/src/main/res/values/strings.xml | 3 +- 13 files changed, 370 insertions(+), 206 deletions(-) create mode 100644 presentation/src/main/java/com/everymeal/presentation/model/Restaurant.kt delete mode 100644 presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreenContract.kt delete mode 100644 presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreenViewModel.kt create mode 100644 presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewViewModel.kt create mode 100644 presentation/src/main/java/com/everymeal/presentation/util/ViewModelExtension.kt diff --git a/domain/src/main/java/com/everymeal/domain/usecase/review/PostReviewUseCase.kt b/domain/src/main/java/com/everymeal/domain/usecase/review/PostReviewUseCase.kt index 4584e5a5..1f35c675 100644 --- a/domain/src/main/java/com/everymeal/domain/usecase/review/PostReviewUseCase.kt +++ b/domain/src/main/java/com/everymeal/domain/usecase/review/PostReviewUseCase.kt @@ -7,7 +7,6 @@ import javax.inject.Inject class PostReviewUseCase @Inject constructor( private val reviewRepository: ReviewRepository ) { - suspend operator fun invoke(userReview: UserReview) { - reviewRepository.postReview(userReview) - } + suspend operator fun invoke(userReview: UserReview) = reviewRepository.postReview(userReview) + } diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index a3dd410e..68769312 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -4,6 +4,7 @@ plugins { id("dagger.hilt.android.plugin") id("kotlin-android") id("kotlin-kapt") + id("kotlin-parcelize") } android { diff --git a/presentation/src/main/java/com/everymeal/presentation/model/Restaurant.kt b/presentation/src/main/java/com/everymeal/presentation/model/Restaurant.kt new file mode 100644 index 00000000..79ed8b56 --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/model/Restaurant.kt @@ -0,0 +1,19 @@ +package com.everymeal.presentation.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class Restaurant( + val idx: Int, + val name: String, + val address: String, + val phoneNumber: String, + val categoryDetail: String, + val distance: Int, + val grade: Float, + val reviewCount: Int, + val recommendedCount: Int, + val images: List?, + val isLiked: Boolean +) : Parcelable diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/bottom/BottomNavigation.kt b/presentation/src/main/java/com/everymeal/presentation/ui/bottom/BottomNavigation.kt index 73c11d42..e6858cee 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/bottom/BottomNavigation.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/bottom/BottomNavigation.kt @@ -41,7 +41,8 @@ enum class EveryMealRoute(val route: String) { DETAIL_RESTAURANT("detail-restaurant"), SCHOOL_AUTH("school-auth"), PROFILE_GENERATE("profile_generate?emailAuthValue={emailAuthValue}&emailAuthToken={emailAuthToken}"), - SIGN_UP_SUCCESS("sign-up-success"), WITH_DRAW("with-draw"), REVIEW_SEARCH("review-search"), + REVIEW("review"), + REVIEW_WRITE("review-write"), } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/main/MainScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/main/MainScreen.kt index eff7e459..a0892c69 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/main/MainScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/main/MainScreen.kt @@ -6,9 +6,12 @@ import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavBackStackEntry import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -23,12 +26,16 @@ import com.everymeal.presentation.ui.home.HomeScreen import com.everymeal.presentation.ui.mypage.MyPageScreen import com.everymeal.presentation.ui.profile.ProfileGenerateScreen import com.everymeal.presentation.ui.restaurant.DetailRestaurantScreen +import com.everymeal.presentation.ui.review.ReviewScreen +import com.everymeal.presentation.ui.review.ReviewViewModel import com.everymeal.presentation.ui.review.search.ReviewSearchScreen +import com.everymeal.presentation.ui.review.write.ReviewWriteScreen import com.everymeal.presentation.ui.search.SearchScreen import com.everymeal.presentation.ui.signup.school.SchoolAuthScreen import com.everymeal.presentation.ui.univfood.UnivFoodScreen import com.everymeal.presentation.ui.whatfood.WhatFoodScreen import com.everymeal.presentation.ui.withdraw.WithDrawScreen +import com.everymeal.presentation.util.sharedViewModel const val DETAIL_SCREEN_TYPE = "detailScreenType" const val DETAIL_RESTAURANT_IDX = "detailRestaurantIdx" @@ -133,6 +140,21 @@ fun MainScreen( composable(route = EveryMealRoute.REVIEW_SEARCH.route) { ReviewSearchScreen(navController = navController) } + composable(route = EveryMealRoute.REVIEW.route.plus("/{$DETAIL_RESTAURANT_IDX}")) { navBackStackEntry -> + val argument = navBackStackEntry.arguments + val restaurantIdx = argument?.getString(DETAIL_RESTAURANT_IDX).orEmpty() + val viewModel = navBackStackEntry.sharedViewModel( + navController = navController, + navGraphRoute = EveryMealRoute.HOME.route + ) + ReviewScreen( + viewModel = viewModel, + restaurantIdx = restaurantIdx, + moveReviewWriteScreen = { + navController.navigate(EveryMealRoute.REVIEW_WRITE.route) + } + ) + } composable(route = EveryMealRoute.WITH_DRAW.route) { WithDrawScreen( onBackClick = { @@ -141,7 +163,17 @@ fun MainScreen( ) } composable(route = EveryMealRoute.SEARCH.route) { - SearchScreen(navController = navController) + SearchScreen( + moveReviewScreen = { restaurantIdx -> + navController.navigate(EveryMealRoute.REVIEW.route.plus("/$restaurantIdx")) + }) + } + composable(route = EveryMealRoute.REVIEW_WRITE.route) { navBackStackEntry -> + val viewModel = navBackStackEntry.sharedViewModel( + navController = navController, + navGraphRoute = EveryMealRoute.HOME.route + ) + ReviewWriteScreen(viewModel = viewModel) } } } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreen.kt index 8e5dcee4..919816c3 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreen.kt @@ -1,16 +1,15 @@ package com.everymeal.presentation.ui.review -import android.util.Log -import android.widget.Toast -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.PickVisualMediaRequest -import androidx.activity.result.contract.ActivityResultContracts 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.ColumnScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +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.lazy.LazyRow @@ -30,7 +29,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -42,24 +40,22 @@ import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import com.everymeal.presentation.R import com.everymeal.presentation.components.EveryMealDialog -import com.everymeal.presentation.ui.review.write.ReviewWriteScreen +import com.everymeal.presentation.ui.review.search.ReviewGuideHeader import com.everymeal.presentation.ui.theme.Gray600 import com.everymeal.presentation.ui.theme.Grey2 import com.everymeal.presentation.ui.theme.Grey9 import com.everymeal.presentation.ui.theme.Typography +import com.everymeal.presentation.util.noRippleClickable @Composable fun ReviewScreen( - viewModel: ReviewScreenViewModel = hiltViewModel(), + viewModel: ReviewViewModel, + restaurantIdx: String, + moveReviewWriteScreen: () -> Unit = {}, ) { - val pickMultipleMedia = rememberLauncherForActivityResult( - ActivityResultContracts.PickMultipleVisualMedia(10), - ) { - viewModel.setEvent(ReviewEvent.OnImageSelected(it)) - Log.d("ReviewScreen", "ReviewScreen: $it") - } + viewModel.setEvent(ReviewEvent.SetRestaurantIdx(restaurantIdx.toIntOrNull() ?: 0)) + val viewState by viewModel.viewState.collectAsState() - val context = LocalContext.current Scaffold( topBar = { ReviewTopBar( @@ -69,81 +65,64 @@ fun ReviewScreen( }, containerColor = Color.White, ) { innerPadding -> + ReviewStarRateContract( + modifier = Modifier.padding(innerPadding), + viewState = viewState, + onStarClicked = { index -> + viewModel.setEvent(ReviewEvent.OnStarClicked(index)) + }, + moveReviewWriteScreen = moveReviewWriteScreen, + ) Box(modifier = Modifier.padding(innerPadding)) { - Column { -// ReviewGuideHeader() -// ReviewSearchBar( -// modifier = Modifier -// .padding(top = 28.dp) -// .padding(horizontal = 20.dp), -// searchBarClicked = { -// //TODO 화면 이동 -// } -// ) -// StarDetail( -// viewState = viewState, -// startRatingClicked = { index -> -// viewModel.setEvent(ReviewEvent.OnStarClicked(index)) -// }, -// ) - ReviewWriteScreen( - viewState = viewState, - starRatingClicked = { index -> - viewModel.setEvent(ReviewEvent.OnStarClicked(index)) - }, - reviewTextChanged = { - viewModel.setEvent(ReviewEvent.OnReviewTextChanged(it)) - }, - onReviewRegisterClicked = { - viewModel.setEvent( - ReviewEvent.PostReview( - mealIdx = viewState.idx, - reviewValue = viewState.reviewValue, - imageUri = viewState.imageUri, - restaurantType = viewState.restaurantType, - restaurantName = viewState.restaurantName, - starRatingCount = viewState.starRatingStateList.count { it.value }, - ), - ) - Toast.makeText( - context, - context.getString(R.string.register_review), - Toast.LENGTH_SHORT, - ).show() - }, - onAddPhotoClicked = { - pickMultipleMedia.launch( - PickVisualMediaRequest( - ActivityResultContracts.PickVisualMedia.ImageOnly, - ), - ) - }, - ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + } } } } @Composable -fun ColumnScope.StarDetail( +private fun ReviewStarRateContract( modifier: Modifier = Modifier, viewState: ReviewState, - startRatingClicked: (Int) -> Unit, + onStarClicked: (Int) -> Unit = {}, + moveReviewWriteScreen: () -> Unit = {}, ) { Column( - modifier = modifier - .align(Alignment.CenterHorizontally) - .padding(top = 133.dp), + modifier = modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally ) { - RestaurantType( - viewState = viewState, - ) - RestaurantName( + ReviewGuideHeader(modifier = Modifier.fillMaxWidth()) + Spacer(modifier = Modifier.height(133.dp)) + StarDetail( + modifier = Modifier, viewState = viewState, + startRatingClicked = { index -> + onStarClicked(index) + moveReviewWriteScreen() + }, ) + } + +} + +@Composable +fun StarDetail( + modifier: Modifier, + viewState: ReviewState, + startRatingClicked: (Int) -> Unit, +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + RestaurantType(viewState = viewState) + RestaurantName(viewState = viewState) + Spacer(modifier = Modifier.height(50.dp)) StarRating( - modifier = Modifier - .padding(top = 50.dp), + modifier = Modifier, ratingStateList = viewState.starRatingStateList, starRatingClicked = startRatingClicked, ) @@ -194,7 +173,7 @@ fun RestaurantName( fun StarRating( modifier: Modifier = Modifier, ratingStateList: List>, - starRatingClicked: ((Int) -> Unit)? = null, + starRatingClicked: (Int) -> Unit = {}, starSize: Dp = 40.dp, ) { LazyRow( @@ -206,8 +185,8 @@ fun StarRating( modifier = Modifier .size(starSize) .padding(horizontal = 1.dp) - .clickable { - starRatingClicked?.invoke(index) + .noRippleClickable { + starRatingClicked(index) }, painter = if (active.value) { painterResource( @@ -279,5 +258,14 @@ fun ReviewSaveDialog( @Composable @Preview fun ReviewScreenPreview() { + ReviewScreen( + viewModel = hiltViewModel(), + restaurantIdx = "1" + ) +} + +@Composable +@Preview +fun ReviewSaveDialogPreview() { ReviewSaveDialog() } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreenContract.kt b/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreenContract.kt deleted file mode 100644 index 4f9d8ea0..00000000 --- a/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreenContract.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.everymeal.presentation.ui.review - -import android.net.Uri -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import com.everymeal.presentation.base.ViewEvent -import com.everymeal.presentation.base.ViewSideEffect -import com.everymeal.presentation.base.ViewState - -data class ReviewState( - val starRatingStateList: List> = listOf( - mutableStateOf(false), - mutableStateOf(false), - mutableStateOf(false), - mutableStateOf(false), - mutableStateOf(false), - ), - val imageUri: List = listOf(), - val idx: Int = 0, - val restaurantType: String = "주점", - val restaurantName: String = "성신 이자카야", - val reviewValue: String = "", -) : ViewState - -sealed class ReviewEvent : ViewEvent { - data class OnStarClicked( - val starIndex: Int, - ) : ReviewEvent() - - data class OnReviewTextChanged( - val reviewValue: String, - ) : ReviewEvent() - - data class OnImageSelected( - val imageUri: List, - ) : ReviewEvent() - - data class PostReview( - val mealIdx: Int, - val reviewValue: String, - val imageUri: List, - val restaurantType: String, - val restaurantName: String, - val starRatingCount: Int, - ) : ReviewEvent() -} - -sealed class ReviewEffect : ViewSideEffect diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreenViewModel.kt b/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreenViewModel.kt deleted file mode 100644 index 84ba725e..00000000 --- a/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreenViewModel.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.everymeal.presentation.ui.review - -import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.viewModelScope -import com.everymeal.domain.model.review.UserReview -import com.everymeal.domain.usecase.review.PostReviewUseCase -import com.everymeal.presentation.base.BaseViewModel -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class ReviewScreenViewModel @Inject constructor( - private val postReviewUseCase: PostReviewUseCase, -) : BaseViewModel(ReviewState()) { - override fun handleEvents(event: ReviewEvent) { - when (event) { - is ReviewEvent.OnStarClicked -> { - updateState { - val newStarRatingStateList = List(starRatingStateList.size) { index -> - mutableStateOf(index <= event.starIndex) - } - copy( - starRatingStateList = newStarRatingStateList, - ) - } - } - - is ReviewEvent.OnReviewTextChanged -> { - updateState { - copy( - reviewValue = event.reviewValue, - ) - } - } - - is ReviewEvent.OnImageSelected -> { - updateState { - copy( - imageUri = event.imageUri, - ) - } - } - - is ReviewEvent.PostReview -> { - viewModelScope.launch { - val userReview = UserReview( - idx = event.mealIdx, - grade = event.starRatingCount, - content = event.reviewValue, - imageList = event.imageUri.map { it.toString() }, - ) - postReviewUseCase(userReview) - } - } - } - } -} diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewViewModel.kt b/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewViewModel.kt new file mode 100644 index 00000000..98657f82 --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewViewModel.kt @@ -0,0 +1,137 @@ +package com.everymeal.presentation.ui.review + +import android.net.Uri +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.viewModelScope +import com.everymeal.domain.model.review.UserReview +import com.everymeal.domain.usecase.restaurant.GetDetailRestaurantUseCase +import com.everymeal.domain.usecase.review.PostReviewUseCase +import com.everymeal.presentation.base.BaseViewModel +import com.everymeal.presentation.base.ViewEvent +import com.everymeal.presentation.base.ViewSideEffect +import com.everymeal.presentation.base.ViewState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +data class ReviewState( + val starRatingStateList: List> = listOf( + mutableStateOf(false), + mutableStateOf(false), + mutableStateOf(false), + mutableStateOf(false), + mutableStateOf(false), + ), + val imageUri: List = listOf(), + val restaurantIdx: Int = 0, + val restaurantType: String = "주점", + val restaurantName: String = "성신 이자카야", + val reviewValue: String = "", +) : ViewState + +sealed class ReviewEvent : ViewEvent { + data class SetRestaurantIdx( + val idx: Int, + ) : ReviewEvent() + + data class OnStarClicked( + val starIndex: Int, + ) : ReviewEvent() + + data class OnReviewTextChanged( + val reviewValue: String, + ) : ReviewEvent() + + data class OnImageSelected( + val imageUri: List, + ) : ReviewEvent() + + object OnReviewSave : ReviewEvent() +} + +sealed class ReviewEffect : ViewSideEffect { + object OnReviewSaveSuccess : ReviewEffect() + + object OnReviewSaveFail : ReviewEffect() +} + + +@HiltViewModel +class ReviewViewModel @Inject constructor( + private val postReviewUseCase: PostReviewUseCase, + private val getDetailRestaurantUseCase: GetDetailRestaurantUseCase +) : BaseViewModel(ReviewState()) { + override fun handleEvents(event: ReviewEvent) { + when (event) { + is ReviewEvent.SetRestaurantIdx -> { + getRestaurantDetail(event.idx) + } + + is ReviewEvent.OnStarClicked -> { + updateState { + val newStarRatingStateList = List(starRatingStateList.size) { index -> + mutableStateOf(index <= event.starIndex) + } + copy( + starRatingStateList = newStarRatingStateList, + ) + } + } + + is ReviewEvent.OnReviewTextChanged -> { + updateState { + copy( + reviewValue = event.reviewValue, + ) + } + } + + is ReviewEvent.OnImageSelected -> { + updateState { + copy( + imageUri = event.imageUri, + ) + } + } + + is ReviewEvent.OnReviewSave -> { + reviewSave() + } + + } + } + + private fun getRestaurantDetail(restaurantIdx: Int) { + viewModelScope.launch { + getDetailRestaurantUseCase(restaurantIdx).onSuccess { + updateState { + copy( + restaurantIdx = it.idx, + restaurantName = it.name, + restaurantType = it.categoryDetail, + ) + } + } + } + } + + private fun reviewSave() { + val viewState = viewState.value + val grade = viewState.starRatingStateList.count { it.value } + val imageList = viewState.imageUri.map { it.toString() } + viewModelScope.launch { + val userReview = UserReview( + idx = viewState.restaurantIdx, + grade = grade, + content = viewState.reviewValue, + imageList = imageList, + ) + postReviewUseCase(userReview).onSuccess { + sendEffect({ ReviewEffect.OnReviewSaveSuccess }) + }.onFailure { + sendEffect({ ReviewEffect.OnReviewSaveFail }) + } + } + } +} diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/review/write/ReviewWriteScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/review/write/ReviewWriteScreen.kt index 8cc9fad9..7c1a1895 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/review/write/ReviewWriteScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/review/write/ReviewWriteScreen.kt @@ -1,5 +1,9 @@ package com.everymeal.presentation.ui.review.write +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -18,38 +22,110 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface 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.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import coil.compose.AsyncImage import com.everymeal.presentation.R import com.everymeal.presentation.components.EveryMealTextField import com.everymeal.presentation.ui.review.RestaurantName import com.everymeal.presentation.ui.review.RestaurantType +import com.everymeal.presentation.ui.review.ReviewEffect +import com.everymeal.presentation.ui.review.ReviewEvent import com.everymeal.presentation.ui.review.ReviewState +import com.everymeal.presentation.ui.review.ReviewTopBar +import com.everymeal.presentation.ui.review.ReviewViewModel import com.everymeal.presentation.ui.review.StarRating import com.everymeal.presentation.ui.theme.Gray200 import com.everymeal.presentation.ui.theme.Main100 @Composable fun ReviewWriteScreen( + viewModel: ReviewViewModel, +) { + val pickMultipleMedia = rememberLauncherForActivityResult( + ActivityResultContracts.PickMultipleVisualMedia(10), + ) { + viewModel.setEvent(ReviewEvent.OnImageSelected(it)) + } + val viewState by viewModel.viewState.collectAsState() + val context = LocalContext.current + + Scaffold( + topBar = { + ReviewTopBar( + title = stringResource(R.string.review_write), + onBackClicked = {}, + ) + }, + containerColor = Color.White, + ) { innerPadding -> + ReviewWriteContract( + modifier = Modifier.padding(innerPadding), + viewState = viewState, + reviewTextChanged = { + viewModel.setEvent(ReviewEvent.OnReviewTextChanged(it)) + }, + onAddPhotoClicked = { + pickMultipleMedia.launch( + PickVisualMediaRequest( + ActivityResultContracts.PickVisualMedia.ImageOnly, + ), + ) + }, + onReviewRegisterClicked = { + viewModel.setEvent(ReviewEvent.OnReviewSave) + } + ) + } + LaunchedEffect(Unit) { + viewModel.effect.collect { + when (it) { + is ReviewEffect.OnReviewSaveSuccess -> { + Toast.makeText( + context, + context.getString(R.string.register_review_success), + Toast.LENGTH_SHORT, + ).show() + } + + is ReviewEffect.OnReviewSaveFail -> { + Toast.makeText( + context, + context.getString(R.string.register_review_fail), + Toast.LENGTH_SHORT, + ).show() + } + } + } + + } +} + +@Composable +private fun ReviewWriteContract( modifier: Modifier = Modifier, viewState: ReviewState, - starRatingClicked: (Int) -> Unit, - reviewTextChanged: (String) -> Unit, - onReviewRegisterClicked: () -> Unit, - onAddPhotoClicked: () -> Unit, + reviewTextChanged: (String) -> Unit = {}, + onAddPhotoClicked: () -> Unit = {}, + onReviewRegisterClicked: () -> Unit = {}, ) { Column( modifier = modifier @@ -58,10 +134,8 @@ fun ReviewWriteScreen( .padding(horizontal = 20.dp), horizontalAlignment = Alignment.CenterHorizontally, - ) { - RestaurantType( - viewState = viewState, - ) + ) { + RestaurantType(viewState = viewState) RestaurantName( modifier = Modifier .padding(top = 12.dp), @@ -70,7 +144,6 @@ fun ReviewWriteScreen( StarRating( modifier = modifier.padding(top = 16.dp), ratingStateList = viewState.starRatingStateList, - starRatingClicked = starRatingClicked, starSize = 20.dp, ) Spacer(modifier = Modifier.height(60.dp)) diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/search/SearchScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/search/SearchScreen.kt index 13ba2e23..d87d4df5 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/search/SearchScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/search/SearchScreen.kt @@ -39,8 +39,8 @@ import com.everymeal.presentation.ui.theme.Gray800 @Composable fun SearchScreen( - navController: NavController, viewModel: SearchViewModel = hiltViewModel(), + moveReviewScreen: (Int) -> Unit = {}, ) { val viewState = viewModel.viewState.collectAsState() @@ -77,6 +77,7 @@ fun SearchScreen( SearchDetail( modifier = Modifier.padding(innerPadding), searchResultList = viewState.value.searchResultList.collectAsLazyPagingItems(), + onRestaurantDetailClick = moveReviewScreen ) } } @@ -86,6 +87,7 @@ fun SearchScreen( fun SearchDetail( modifier: Modifier = Modifier, searchResultList: LazyPagingItems, + onRestaurantDetailClick: (Int) -> Unit = {}, ) { LazyColumn(modifier = modifier) { items(searchResultList.itemCount) { indexd -> @@ -95,9 +97,7 @@ fun SearchDetail( onLoveClick = { }, - onDetailClick = { - - }, + onDetailClick = onRestaurantDetailClick, ) } } @@ -145,7 +145,5 @@ private fun removeHistoryItem( @Preview @Composable fun PreviewSearchScreen() { - SearchScreen( - navController = NavController(LocalContext.current), - ) + SearchScreen() } diff --git a/presentation/src/main/java/com/everymeal/presentation/util/ViewModelExtension.kt b/presentation/src/main/java/com/everymeal/presentation/util/ViewModelExtension.kt new file mode 100644 index 00000000..3961bfec --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/util/ViewModelExtension.kt @@ -0,0 +1,21 @@ +package com.everymeal.presentation.util + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.ViewModel +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController + + +@Composable +inline fun NavBackStackEntry.sharedViewModel( + navController: NavController, + navGraphRoute: String, +): T { + val parentEntry = remember(this) { + navController.getBackStackEntry(navGraphRoute) + } + + return hiltViewModel(parentEntry) +} diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index fee4807e..7be3b577 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -113,7 +113,8 @@ 공유하기 사진만 올리기 리뷰 작성하기 - 리뷰가 등록되었어요 + 리뷰가 등록되었어요 + 리뷰 등록에 실패했어요 인증하기