diff --git a/data/src/main/java/com/everymeal/data/datasource/review/ReviewDataSource.kt b/data/src/main/java/com/everymeal/data/datasource/review/ReviewDataSource.kt index 327428b2..938758b9 100644 --- a/data/src/main/java/com/everymeal/data/datasource/review/ReviewDataSource.kt +++ b/data/src/main/java/com/everymeal/data/datasource/review/ReviewDataSource.kt @@ -1,13 +1,28 @@ package com.everymeal.data.datasource.review +import androidx.paging.PagingData import com.everymeal.data.model.review.ReviewListResponse +import com.everymeal.data.model.review.ReviewResponse import com.everymeal.data.model.review.StoreReviewRequest +import com.everymeal.domain.model.review.GetStoreReviewEntity +import kotlinx.coroutines.flow.Flow interface ReviewDataSource { - suspend fun getReviewList( - cursorIdx: Int, - mealIdx: Int, - pageSize: Int, + + suspend fun getPagingStoreReviews( + order: String?, + group: String?, + grade: Int?, + campusIdx: Int, + ): Flow> + + suspend fun getStoreReviews( + offset: Int, + limit: Int, + order: String?, + group: String?, + grade: Int?, + campusIdx: Int, ): Result suspend fun postReview(storeReviewRequest: StoreReviewRequest): Result diff --git a/data/src/main/java/com/everymeal/data/datasource/review/ReviewDataSourceImpl.kt b/data/src/main/java/com/everymeal/data/datasource/review/ReviewDataSourceImpl.kt index 3aca7eb3..3024f42f 100644 --- a/data/src/main/java/com/everymeal/data/datasource/review/ReviewDataSourceImpl.kt +++ b/data/src/main/java/com/everymeal/data/datasource/review/ReviewDataSourceImpl.kt @@ -1,27 +1,54 @@ package com.everymeal.data.datasource.review +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.everymeal.data.datasource.restaurant.PAGING_SIZE import com.everymeal.data.model.review.ReviewListResponse +import com.everymeal.data.model.review.ReviewResponse import com.everymeal.data.model.review.StoreReviewRequest import com.everymeal.data.model.unwrapRunCatching import com.everymeal.data.service.review.StoreReviewApi +import kotlinx.coroutines.flow.Flow import javax.inject.Inject class ReviewDataSourceImpl @Inject constructor( private val storeReviewApi: StoreReviewApi, ) : ReviewDataSource { + override suspend fun getPagingStoreReviews( + order: String?, + group: String?, + grade: Int?, + campusIdx: Int + ): Flow> { + val pagingSourceFactory = { ReviewPagingSource(storeReviewApi, order, group, grade, campusIdx) } + return Pager( + config = PagingConfig( + initialLoadSize = PAGING_SIZE, + pageSize = PAGING_SIZE, + enablePlaceholders = false, + ), + pagingSourceFactory = pagingSourceFactory + ).flow + } - override suspend fun getReviewList( - cursorIdx: Int, - mealIdx: Int, - pageSize: Int, - ): Result = - unwrapRunCatching { - storeReviewApi.getStoreReviewsWithId( - cursorIdx, - mealIdx, - pageSize, - ) - } + override suspend fun getStoreReviews( + offset: Int, + limit: Int, + order: String?, + group: String?, + grade: Int?, + campusIdx: Int + ): Result { + return unwrapRunCatching { storeReviewApi.getStoresReviews( + offset = 0, + limit = 3, + order = order, + group = group, + grade = grade, + campusIdx = campusIdx + ) } + } override suspend fun postReview( storeReviewRequest: StoreReviewRequest, diff --git a/data/src/main/java/com/everymeal/data/datasource/review/ReviewPagingSource.kt b/data/src/main/java/com/everymeal/data/datasource/review/ReviewPagingSource.kt new file mode 100644 index 00000000..99337d6d --- /dev/null +++ b/data/src/main/java/com/everymeal/data/datasource/review/ReviewPagingSource.kt @@ -0,0 +1,53 @@ +package com.everymeal.data.datasource.review + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.everymeal.data.datasource.restaurant.PAGING_SIZE +import com.everymeal.data.datasource.restaurant.STARTING_PAGE_INDEX +import com.everymeal.data.model.restaruant.RestaurantResponse +import com.everymeal.data.model.review.ReviewResponse +import com.everymeal.data.service.restaurant.RestaurantApi +import com.everymeal.data.service.review.StoreReviewApi +import retrofit2.HttpException +import java.io.IOException + +class ReviewPagingSource ( + private val storeReviewApi: StoreReviewApi, + private val order: String?, + private val group: String?, + private val grade: Int?, + private val campusIdx: Int +) : PagingSource() { + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) + ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) + } + } + + override suspend fun load(params: LoadParams): LoadResult { + val position = params.key ?: STARTING_PAGE_INDEX + + return try { + val response = storeReviewApi.getStoresReviews( + campusIdx = campusIdx, + order = order, + group = group, + grade = grade, + offset = position, + limit = PAGING_SIZE + ) + val restaurants = response.data.content + LoadResult.Page( + data = restaurants, + prevKey = if (position == STARTING_PAGE_INDEX) null else position - 1, + nextKey = if (restaurants.isEmpty()) null else position + 1 + ) + } catch (exception: IOException) { + LoadResult.Error(exception) + } catch (exception: HttpException) { + LoadResult.Error(exception) + } + } + +} \ No newline at end of file diff --git a/data/src/main/java/com/everymeal/data/model/review/ReviewListResponse.kt b/data/src/main/java/com/everymeal/data/model/review/ReviewListResponse.kt index e4861abe..28356058 100644 --- a/data/src/main/java/com/everymeal/data/model/review/ReviewListResponse.kt +++ b/data/src/main/java/com/everymeal/data/model/review/ReviewListResponse.kt @@ -1,54 +1,75 @@ package com.everymeal.data.model.review -import com.everymeal.domain.model.review.Review -import kotlinx.serialization.SerialName +import com.everymeal.domain.model.review.GetStoreReviewEntity +import com.everymeal.domain.model.review.StoreReviewEntity import kotlinx.serialization.Serializable @Serializable data class ReviewListResponse( - @SerialName("reviewPagingList") - val reviewPagingList: List? = null, - @SerialName("reviewTotalCnt") - val reviewTotalCnt: Int? = null -) { - @Serializable - data class ReviewPaging( - @SerialName("content") - val content: String? = null, - @SerialName("grade") - val grade: Int? = null, - @SerialName("imageList") - val imageList: List? = null, - @SerialName("mealCategory") - val mealCategory: String? = null, - @SerialName("mealType") - val mealType: String? = null, - @SerialName("restaurantName") - val restaurantName: String? = null, - @SerialName("reviewIdx") - val reviewIdx: Int? = null, - @SerialName("reviewMarksCnt") - val reviewMarksCnt: Int? = null - ) { - fun toReviewDetail(): Review.ReviewDetail { - return Review.ReviewDetail( - content = content ?: "", - grade = grade ?: 0, - imageList = imageList ?: listOf(), - mealCategory = mealCategory ?: "", - mealType = mealType ?: "", - restaurantName = restaurantName ?: "", - reviewIdx = reviewIdx ?: 0, - reviewMarksCnt = reviewMarksCnt ?: 0 - ) - } - } - - fun toReview(): Review { - return Review( - reviewPagingList = reviewPagingList?.map { it.toReviewDetail() } ?: listOf(), - reviewTotalCnt = reviewTotalCnt ?: 0 - ) - } + val content: List, + val pageable: Pageable, + val totalPages: Int, + val totalElements: Int, + val last: Boolean, + val size: Int, + val number: Int, + val sort: Sort, + val numberOfElements: Int, + val first: Boolean, + val empty: Boolean, +) + +@Serializable +data class ReviewResponse( + val reviewIdx: Int, + val content: String, + val grade: Int, + val createdAt: String, + val formattedCreatedAt: String, + val nickName: String, + val profileImageUrl: String, + val reviewMarksCnt: Int, + val images: List?, + val storeIdx: Int, + val storeName: String, +) + +@Serializable +data class Pageable( + val sort: Sort, + val offset: Int, + val pageNumber: Int, + val pageSize: Int, + val paged: Boolean, + val unpaged: Boolean, +) + +@Serializable +data class Sort( + val empty: Boolean, + val sorted: Boolean, + val unsorted: Boolean, +) + +fun ReviewResponse.toStoreReviewEntity(): StoreReviewEntity { + return StoreReviewEntity( + reviewIdx = this.reviewIdx, + content = this.content, + grade = this.grade, + createdAt = this.createdAt, + formattedCreatedAt = this.formattedCreatedAt, + nickName = this.nickName, + profileImageUrl = this.profileImageUrl, + reviewMarksCnt = this.reviewMarksCnt, + images = this.images, + storeIdx = this.storeIdx, + storeName = this.storeName, + ) +} + +fun ReviewListResponse.toStoreReviewEntityList(): GetStoreReviewEntity { + return GetStoreReviewEntity( + data = this.content.map { it.toStoreReviewEntity() } + ) } diff --git a/data/src/main/java/com/everymeal/data/repository/review/DefaultReviewRepository.kt b/data/src/main/java/com/everymeal/data/repository/review/DefaultReviewRepository.kt index 6abb844c..fb69353e 100644 --- a/data/src/main/java/com/everymeal/data/repository/review/DefaultReviewRepository.kt +++ b/data/src/main/java/com/everymeal/data/repository/review/DefaultReviewRepository.kt @@ -1,23 +1,57 @@ package com.everymeal.data.repository.review +import androidx.paging.PagingData +import androidx.paging.map import com.everymeal.data.datasource.review.ReviewDataSource +import com.everymeal.data.model.restaruant.toRestaurant import com.everymeal.data.model.review.toReviewRequest +import com.everymeal.data.model.review.toStoreReviewEntity +import com.everymeal.data.model.review.toStoreReviewEntityList +import com.everymeal.domain.model.review.GetStoreReviewEntity import com.everymeal.domain.model.review.Review +import com.everymeal.domain.model.review.StoreReviewEntity import com.everymeal.domain.model.review.UserReview import com.everymeal.domain.repository.review.ReviewRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import javax.inject.Inject class DefaultReviewRepository @Inject constructor( private val reviewDataSource: ReviewDataSource, ) : ReviewRepository { - override suspend fun getReviewList( - cursorIdx: Int, - mealIdx: Int, - pageSize: Int, - ): Result = - reviewDataSource.getReviewList(cursorIdx, mealIdx, pageSize).map { - it.toReview() + override suspend fun getPagingStoreReviews( + order: String?, + group: String?, + grade: Int?, + campusIdx: Int + ): Flow> { + return reviewDataSource.getPagingStoreReviews( + order, + group, + grade, + campusIdx + ).map { pagingData -> + pagingData.map { it.toStoreReviewEntity() } } + } + + override suspend fun getStoreReviews( + offset: Int, + limit: Int, + order: String?, + group: String?, + grade: Int?, + campusIdx: Int, + ): Result { + return reviewDataSource.getStoreReviews( + offset, + limit, + order, + group, + grade, + campusIdx + ).map { it.toStoreReviewEntityList() } + } override suspend fun postReview( userReview: UserReview, diff --git a/data/src/main/java/com/everymeal/data/service/review/StoreReviewApi.kt b/data/src/main/java/com/everymeal/data/service/review/StoreReviewApi.kt index 06100a94..5150662a 100644 --- a/data/src/main/java/com/everymeal/data/service/review/StoreReviewApi.kt +++ b/data/src/main/java/com/everymeal/data/service/review/StoreReviewApi.kt @@ -9,20 +9,21 @@ import retrofit2.http.POST import retrofit2.http.Query interface StoreReviewApi { - @GET("/api/v1/stores/{index}/reviews") - suspend fun getStoreReviewsWithId( - @Query("index") index: Int, - @Query("offset") offset: Int, - @Query("limit") limit: Int, - ): BaseResponse +// @GET("/api/v1/stores/{index}/reviews") +// suspend fun getStoreReviewsWithId( +// @Query("index") index: Int, +// @Query("offset") offset: Int, +// @Query("limit") limit: Int, +// ): BaseResponse @GET("/api/v1/stores/reviews") suspend fun getStoresReviews( @Query("offset") offset: Int, @Query("limit") limit: Int, - @Query("order") order: String, - @Query("group") group: String, - @Query("grade") grade: Int, + @Query("order") order: String?, + @Query("group") group: String?, + @Query("grade") grade: Int?, + @Query("campusIdx") campusIdx: Int, ): BaseResponse @POST("/api/v1/reviews/store") diff --git a/domain/src/main/java/com/everymeal/domain/model/review/GetStoreReviewEntity.kt b/domain/src/main/java/com/everymeal/domain/model/review/GetStoreReviewEntity.kt new file mode 100644 index 00000000..cf102efd --- /dev/null +++ b/domain/src/main/java/com/everymeal/domain/model/review/GetStoreReviewEntity.kt @@ -0,0 +1,19 @@ +package com.everymeal.domain.model.review + +data class GetStoreReviewEntity( + val data: List +) + +data class StoreReviewEntity( + val reviewIdx: Int, + val content: String, + val grade: Int, + val createdAt: String, + val formattedCreatedAt: String, + val nickName: String, + val profileImageUrl: String, + val reviewMarksCnt: Int, + val images: List?, + val storeIdx: Int, + val storeName: String, +) \ No newline at end of file diff --git a/domain/src/main/java/com/everymeal/domain/repository/review/ReviewRepository.kt b/domain/src/main/java/com/everymeal/domain/repository/review/ReviewRepository.kt index d999da28..7002216f 100644 --- a/domain/src/main/java/com/everymeal/domain/repository/review/ReviewRepository.kt +++ b/domain/src/main/java/com/everymeal/domain/repository/review/ReviewRepository.kt @@ -1,9 +1,27 @@ package com.everymeal.domain.repository.review +import androidx.paging.PagingData +import com.everymeal.domain.model.review.GetStoreReviewEntity import com.everymeal.domain.model.review.Review +import com.everymeal.domain.model.review.StoreReviewEntity import com.everymeal.domain.model.review.UserReview +import kotlinx.coroutines.flow.Flow interface ReviewRepository { - suspend fun getReviewList(cursorIdx: Int, mealIdx: Int, pageSize: Int): Result + suspend fun getPagingStoreReviews( + order: String?, + group: String?, + grade: Int?, + campusIdx: Int, + ): Flow> + + suspend fun getStoreReviews( + offset: Int, + limit: Int, + order: String?, + group: String?, + grade: Int?, + campusIdx: Int, + ): Result suspend fun postReview(userReview: UserReview): Result } diff --git a/domain/src/main/java/com/everymeal/domain/usecase/review/GetHomeReviewUseCase.kt b/domain/src/main/java/com/everymeal/domain/usecase/review/GetHomeReviewUseCase.kt new file mode 100644 index 00000000..80e140a7 --- /dev/null +++ b/domain/src/main/java/com/everymeal/domain/usecase/review/GetHomeReviewUseCase.kt @@ -0,0 +1,20 @@ +package com.everymeal.domain.usecase.review + +import com.everymeal.domain.model.review.GetStoreReviewEntity +import com.everymeal.domain.repository.review.ReviewRepository +import javax.inject.Inject + +class GetHomeReviewUseCase @Inject constructor( + private val reviewRepository: ReviewRepository +) { + suspend operator fun invoke( + offset: Int, + limit: Int, + order: String?, + group: String?, + grade: Int?, + campusIdx: Int + ) : Result { + return reviewRepository.getStoreReviews(offset, limit, order, group, grade, campusIdx) + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/everymeal/domain/usecase/review/GetStoreReviewUseCase.kt b/domain/src/main/java/com/everymeal/domain/usecase/review/GetStoreReviewUseCase.kt new file mode 100644 index 00000000..8700264d --- /dev/null +++ b/domain/src/main/java/com/everymeal/domain/usecase/review/GetStoreReviewUseCase.kt @@ -0,0 +1,20 @@ +package com.everymeal.domain.usecase.review + +import androidx.paging.PagingData +import com.everymeal.domain.model.review.StoreReviewEntity +import com.everymeal.domain.repository.review.ReviewRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetStoreReviewUseCase @Inject constructor( + private val reviewRepository: ReviewRepository +) { + suspend operator fun invoke( + order: String?, + group: String?, + grade: Int?, + campusIdx: Int + ) : Flow> { + return reviewRepository.getPagingStoreReviews(order, group, grade, campusIdx) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/everymeal/presentation/components/EveryMealReviewItem.kt b/presentation/src/main/java/com/everymeal/presentation/components/EveryMealReviewItem.kt index 2d6dd172..04fc5ac5 100644 --- a/presentation/src/main/java/com/everymeal/presentation/components/EveryMealReviewItem.kt +++ b/presentation/src/main/java/com/everymeal/presentation/components/EveryMealReviewItem.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -19,16 +20,17 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale.Companion.Crop import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage +import com.everymeal.domain.model.review.StoreReviewEntity import com.everymeal.presentation.R -import com.everymeal.presentation.ui.home.Review import com.everymeal.presentation.ui.theme.Gray300 import com.everymeal.presentation.ui.theme.Gray600 import com.everymeal.presentation.ui.theme.Gray700 @@ -37,8 +39,8 @@ import com.everymeal.presentation.util.Utils @Composable fun EveryMealReviewItem( - review: Review, - onDetailRestaurantClick: () -> Unit, + review: StoreReviewEntity, + onDetailRestaurantClick: (Int) -> Unit, ) { Column( modifier = Modifier @@ -57,15 +59,15 @@ fun EveryMealReviewItem( } @Composable -fun ReviewTitle(review: Review) { +fun ReviewTitle(review: StoreReviewEntity) { Row( modifier = Modifier.fillMaxWidth(), ) { - Image( + AsyncImage( modifier = Modifier .size(40.dp) .align(alignment = Alignment.CenterVertically), - painter = painterResource(id = review.profileImage), + model = review.profileImageUrl, contentDescription = stringResource(id = R.string.home_review_profile_image_description) ) Spacer(modifier = Modifier.padding(end = 8.dp)) @@ -75,7 +77,7 @@ fun ReviewTitle(review: Review) { .align(alignment = Alignment.CenterVertically) ) { Text( - text = review.name, + text = review.nickName, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = Gray800, @@ -86,7 +88,7 @@ fun ReviewTitle(review: Review) { .padding(top = 4.dp), verticalAlignment = Alignment.CenterVertically, ) { - items(review.rating) { + items(review.grade) { Image( modifier = Modifier .size(12.dp) @@ -98,7 +100,7 @@ fun ReviewTitle(review: Review) { item { Spacer(modifier = Modifier.padding(end = 4.dp)) Text( - text = Utils.getTimeAgo(review.reviewDate), + text = Utils.getTimeAgo(review.createdAt), fontSize = 12.sp, color = Gray600, ) @@ -116,13 +118,12 @@ fun ReviewTitle(review: Review) { } @Composable -fun ReviewContent(review: Review) { +fun ReviewContent(review: StoreReviewEntity) { Row( modifier = Modifier.fillMaxWidth(), ) { Text( - modifier = Modifier - .weight(1f), + modifier = Modifier.weight(1f), text = review.content, fontSize = 14.sp, color = Gray800, @@ -130,18 +131,23 @@ fun ReviewContent(review: Review) { overflow = TextOverflow.Ellipsis ) Spacer(modifier = Modifier.padding(end = 10.dp)) - Image( - modifier = Modifier - .size(64.dp) - .align(alignment = Alignment.CenterVertically), - painter = painterResource(id = review.image[0]), - contentDescription = stringResource(id = R.string.home_review_image) - ) + review.images?.let { + AsyncImage( + modifier = Modifier + .size(64.dp) + .aspectRatio(1f) + .clip(RoundedCornerShape(8.dp)) + .align(alignment = Alignment.CenterVertically), + model = it[0], + contentScale = Crop, + contentDescription = stringResource(id = R.string.home_review_image) + ) + } } } @Composable -fun ReviewGoodCount(review: Review) { +fun ReviewGoodCount(review: StoreReviewEntity) { Row( modifier = Modifier .fillMaxWidth(), @@ -155,7 +161,7 @@ fun ReviewGoodCount(review: Review) { ) Spacer(modifier = Modifier.padding(end = 2.dp)) Text( - text = review.loveCount.toString(), + text = review.reviewMarksCnt.toString(), fontSize = 12.sp, color = Gray600, ) @@ -164,14 +170,14 @@ fun ReviewGoodCount(review: Review) { @Composable fun ReviewDetailRestaurant( - review: Review, - onDetailRestaurantClick: () -> Unit + review: StoreReviewEntity, + onDetailRestaurantClick: (Int) -> Unit ) { Row( modifier = Modifier .clip(RoundedCornerShape(8.dp)) .fillMaxWidth() - .clickable { onDetailRestaurantClick() } + .clickable { onDetailRestaurantClick(review.storeIdx) } .background(Gray300), verticalAlignment = Alignment.CenterVertically, ) { @@ -185,7 +191,7 @@ fun ReviewDetailRestaurant( Text( modifier = Modifier .weight(1f), - text = review.restaurantName, + text = review.storeName, fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = Gray700, @@ -198,25 +204,4 @@ fun ReviewDetailRestaurant( contentDescription = stringResource(id = R.string.home_review_detail_restaurant) ) } -} - -@Preview -@Composable -fun ReviewDetailRestaurantPreview() { - ReviewDetailRestaurant( - review = Review( - name = "슈니", - profileImage = R.drawable.profile_ex_image, - loveCount = 100, - image = listOf( - 0, - 1, - ), - rating = 3, - reviewDate = "2023-08-29T09:58:47.604732", - content = "매장 안쪽으로 가면 너무 감성있는 곳이 나와요. 그리고 분위기도 너무 좋고 맛도 너무 완벽해요. 이런 카페는 정말 처음인 것 같아요. 알바생도 너무 아름답습니다.. 여기 계속 찾을 것 같아요. 정말 항상 감사드려요.", - restaurantName = "왕가주방", - ), - onDetailRestaurantClick = { }, - ) -} +} \ No newline at end of file diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/detail/DetailListContract.kt b/presentation/src/main/java/com/everymeal/presentation/ui/detail/DetailListContract.kt index c9b606d0..ea9b4a5c 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/detail/DetailListContract.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/detail/DetailListContract.kt @@ -16,10 +16,11 @@ class DetailContract { val reportCategoryType: ReportCategoryType = ReportCategoryType.NONE, val restaurantCategoryType: RestaurantCategoryType = RestaurantCategoryType.NONE, val rating: Int = 0, + val isReviewScreen: Boolean = false ) : ViewState sealed class DetailEvent : ViewEvent { - object InitDetailScreen : DetailEvent() + data class InitDetailScreen(val isReviewScreen: Boolean = false) : DetailEvent() data class SortBottomSheetStateChange(val sortBottomSheetState: Boolean) : DetailEvent() data class MealRatingBottomSheetStateChange(val mealRatingBottomSheetState: Boolean) : DetailEvent() data class ReportBottomSheetStateChange(val reportBottomSheetState: Boolean) : DetailEvent() diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/detail/DetailListScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/detail/DetailListScreen.kt index 53776f89..597d6e2b 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/detail/DetailListScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/detail/DetailListScreen.kt @@ -31,11 +31,13 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import com.everymeal.domain.model.restaurant.RestaurantDataEntity +import com.everymeal.domain.model.review.StoreReviewEntity import com.everymeal.presentation.R import com.everymeal.presentation.components.EveryMealCategoryRatingBottomSheetDialog import com.everymeal.presentation.components.EveryMealDetailReportBottomSheetDialog import com.everymeal.presentation.components.EveryMealReportBottomSheetDialog import com.everymeal.presentation.components.EveryMealRestaurantItem +import com.everymeal.presentation.components.EveryMealReviewItem import com.everymeal.presentation.components.EveryMealSortCategoryBottomSheetDialog import com.everymeal.presentation.ui.save.SaveTopBar import com.everymeal.presentation.ui.theme.Grey2 @@ -55,8 +57,11 @@ fun DetailListScreen( val pagingRestaurantList: LazyPagingItems = detailListViewModel.restaurantItems.collectAsLazyPagingItems() + val pagingReviewList: LazyPagingItems = + detailListViewModel.restaurantReviews.collectAsLazyPagingItems() + LaunchedEffect(Unit) { - detailListViewModel.setEvent(DetailContract.DetailEvent.InitDetailScreen) + detailListViewModel.setEvent(DetailContract.DetailEvent.InitDetailScreen(title == "리뷰")) } LaunchedEffect(key1 = detailListViewModel.effect) { @@ -258,6 +263,19 @@ fun DetailListScreen( Spacer(modifier = Modifier.padding(16.dp)) } } + + items(pagingReviewList.itemCount) { index -> + val item = pagingReviewList[index] + item?.let { + EveryMealReviewItem( + review = it, + onDetailRestaurantClick = { restaurantId -> + detailListViewModel.setEvent(DetailContract.DetailEvent.OnRestaurantDetailClick(restaurantId)) + } + ) + Spacer(modifier = Modifier.padding(16.dp)) + } + } } } } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/detail/DetailListViewModel.kt b/presentation/src/main/java/com/everymeal/presentation/ui/detail/DetailListViewModel.kt index 8bf0e46f..0fbda5e2 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/detail/DetailListViewModel.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/detail/DetailListViewModel.kt @@ -4,8 +4,10 @@ import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn import com.everymeal.domain.model.restaurant.RestaurantDataEntity +import com.everymeal.domain.model.review.StoreReviewEntity import com.everymeal.domain.usecase.local.GetUniversityIndexUseCase import com.everymeal.domain.usecase.restaurant.GetUnivRestaurantUseCase +import com.everymeal.domain.usecase.review.GetStoreReviewUseCase import com.everymeal.presentation.base.BaseViewModel import com.everymeal.presentation.ui.detail.DetailContract.DetailEvent import com.everymeal.presentation.ui.detail.DetailContract.DetailState @@ -21,23 +23,30 @@ import javax.inject.Inject @HiltViewModel class DetailListViewModel @Inject constructor( private val getUnivRestaurantUseCase: GetUnivRestaurantUseCase, - private val getUniversityIndexUseCase: GetUniversityIndexUseCase + private val getUniversityIndexUseCase: GetUniversityIndexUseCase, + private val getStoreReviewUseCase: GetStoreReviewUseCase ): BaseViewModel( DetailState() ) { private val _restaurantItems : MutableStateFlow> = MutableStateFlow(value = PagingData.empty()) val restaurantItems : StateFlow> = _restaurantItems.asStateFlow() + private val _restaurantReviews : MutableStateFlow> = MutableStateFlow(value = PagingData.empty()) + val restaurantReviews : StateFlow> = _restaurantReviews.asStateFlow() + override fun handleEvents(event: DetailEvent) { when (event) { is DetailEvent.InitDetailScreen -> { - getRestaurantList() + reflectUpdateState( + isReviewScreen = event.isReviewScreen + ) + fetchListBasedOnType(event.isReviewScreen) } is DetailEvent.OnClickDetailListCategoryType -> { reflectUpdateState( detailSortCategoryType = event.detailSortCategoryType ) - getRestaurantList() + fetchListBasedOnType(viewState.value.isReviewScreen) } is DetailEvent.SortBottomSheetStateChange -> { reflectUpdateState( @@ -48,7 +57,7 @@ class DetailListViewModel @Inject constructor( reflectUpdateState( mealRatingBottomSheetState = event.mealRatingBottomSheetState ) - getRestaurantList() + fetchListBasedOnType(viewState.value.isReviewScreen) } is DetailEvent.ReportBottomSheetStateChange -> { reflectUpdateState( @@ -79,13 +88,13 @@ class DetailListViewModel @Inject constructor( reflectUpdateState( restaurantCategoryType = RestaurantCategoryType.NONE ) - getRestaurantList() + fetchListBasedOnType(viewState.value.isReviewScreen) } is DetailEvent.OnDeleteClickRating -> { reflectUpdateState( rating = 0 ) - getRestaurantList() + fetchListBasedOnType(viewState.value.isReviewScreen) } is DetailEvent.OnRestaurantDetailClick -> { sendEffect({ DetailEffect.OnRestaurantClickEffect(event.restaurantId) }) @@ -93,6 +102,28 @@ class DetailListViewModel @Inject constructor( } } + private fun fetchListBasedOnType(isReviewScreen: Boolean) { + if(isReviewScreen) { + getReviewList() + } else { + getRestaurantList() + } + } + + private fun getReviewList() { + viewModelScope.launch { + getStoreReviewUseCase( + order = viewState.value.detailSortCategoryType.sort(), + group = viewState.value.restaurantCategoryType.sort(), + grade = if(viewState.value.rating == 0) null else viewState.value.rating, + campusIdx = getUniversityIndexUseCase().first().toInt() + ).cachedIn(viewModelScope) + .collect { + _restaurantReviews.emit(it) + } + } + } + private fun getRestaurantList() { viewModelScope.launch { getUnivRestaurantUseCase( @@ -116,6 +147,7 @@ class DetailListViewModel @Inject constructor( reportCategoryType: ReportCategoryType = viewState.value.reportCategoryType, rating: Int = viewState.value.rating, restaurantCategoryType: RestaurantCategoryType = viewState.value.restaurantCategoryType, + isReviewScreen: Boolean = viewState.value.isReviewScreen ) { updateState { copy( @@ -127,6 +159,7 @@ class DetailListViewModel @Inject constructor( reportCategoryType = reportCategoryType, rating = rating, restaurantCategoryType = restaurantCategoryType, + isReviewScreen = isReviewScreen ) } } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/home/HomeContract.kt b/presentation/src/main/java/com/everymeal/presentation/ui/home/HomeContract.kt index 43a90fa0..01ea35da 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/home/HomeContract.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/home/HomeContract.kt @@ -1,6 +1,7 @@ package com.everymeal.presentation.ui.home import com.everymeal.domain.model.restaurant.RestaurantDataEntity +import com.everymeal.domain.model.review.StoreReviewEntity import com.everymeal.presentation.base.LoadState import com.everymeal.presentation.base.ViewEvent import com.everymeal.presentation.base.ViewSideEffect @@ -11,7 +12,8 @@ class HomeContract { val uiState: LoadState = LoadState.LOADING, val detailListScreenType: DetailListScreenType = DetailListScreenType.RECOMMEND, val bottomSheetState: Boolean = false, - val restaurantData: List = emptyList() + val restaurantData: List = emptyList(), + val reviewData: List = emptyList() ) : ViewState sealed class HomeEvent : ViewEvent { @@ -32,6 +34,7 @@ enum class DetailListScreenType { RESTAURANT, CAFE, DRINK, + REVIEW } fun String.DetailListScreenType(): DetailListScreenType { @@ -40,6 +43,7 @@ fun String.DetailListScreenType(): DetailListScreenType { "밥집" -> DetailListScreenType.RESTAURANT "카페" -> DetailListScreenType.CAFE "술집" -> DetailListScreenType.DRINK + "리뷰" -> DetailListScreenType.REVIEW else -> DetailListScreenType.RECOMMEND } } @@ -50,5 +54,6 @@ fun DetailListScreenType.title(): String { DetailListScreenType.RESTAURANT -> "밥집" DetailListScreenType.CAFE -> "카페" DetailListScreenType.DRINK -> "술집" + DetailListScreenType.REVIEW -> "리뷰" } } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/home/HomeScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/home/HomeScreen.kt index c54a6f5f..fd086101 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/home/HomeScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/home/HomeScreen.kt @@ -80,35 +80,6 @@ fun HomeScreen( } } - val reviewTestItem = listOf( - Review( - name = "슈니", - profileImage = R.drawable.profile_ex_image, - loveCount = 100, - image = listOf( - R.drawable.review_ex_1, - R.drawable.review_ex_1, - ), - rating = 5, - reviewDate = "2023-08-29T09:58:47.604732", - content = "매장 안쪽으로 가면 너무 감성있는 곳이 나와요. 그리고 분위기도 너무 좋고 맛도 너무 완벽해요. 이런 카페는 정말 처음인 것 같아요. 알바생도 너무 아름답습니다.. 여기 계속 찾을 것 같아요. 정말 항상 감사드려요.", - restaurantName = "왕가주방", - ), - Review( - name = "슈니", - profileImage = R.drawable.profile_ex_image, - loveCount = 100, - image = listOf( - R.drawable.review_ex_1, - R.drawable.review_ex_1, - ), - rating = 5, - reviewDate = "2023-08-20T09:58:47.604732", - content = "맛있어요", - restaurantName = "왕가주방", - ), - ) - val homeViewState by homeViewModel.viewState.collectAsState() if (homeViewState.bottomSheetState) { @@ -202,13 +173,17 @@ fun HomeScreen( LazyColumnTitle(stringResource(R.string.home_top_good_review)) Spacer(modifier = Modifier.padding(8.dp)) } - items(reviewTestItem.size) { index -> - val item = reviewTestItem[index] + items(homeViewState.reviewData.size) { index -> + val item = homeViewState.reviewData[index] EveryMealReviewItem(item) { - + homeViewModel.setEvent( + HomeContract.HomeEvent.OnClickDetailRestaurant( + it + ) + ) } Spacer(modifier = Modifier.padding(10.dp)) - if (index != reviewTestItem.size - 1) { + if (index != homeViewState.reviewData.size - 1) { Divider( modifier = Modifier .padding(horizontal = 20.dp), @@ -221,7 +196,7 @@ fun HomeScreen( EveryMealLineButton( text = stringResource(R.string.home_restaurant_review_button_text), onClick = { - + homeViewModel.setEvent(HomeContract.HomeEvent.OnClickDetailList("리뷰".DetailListScreenType())) }, ) } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/home/HomeViewModel.kt b/presentation/src/main/java/com/everymeal/presentation/ui/home/HomeViewModel.kt index a0117795..a0cafadf 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/home/HomeViewModel.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/home/HomeViewModel.kt @@ -1,9 +1,11 @@ package com.everymeal.presentation.ui.home +import android.util.Log import androidx.lifecycle.viewModelScope import com.everymeal.domain.usecase.local.GetUniversityIndexUseCase import com.everymeal.domain.usecase.restaurant.GetHomeRestaurantUseCase import com.everymeal.domain.usecase.restaurant.GetUnivRestaurantUseCase +import com.everymeal.domain.usecase.review.GetHomeReviewUseCase import com.everymeal.presentation.base.BaseViewModel import com.everymeal.presentation.base.LoadState import com.everymeal.presentation.ui.home.HomeContract.HomeEffect @@ -14,30 +16,11 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import javax.inject.Inject -data class Review( - val name: String, - val profileImage: Int, - val loveCount: Int, - val image: List, - val rating: Int, - val reviewDate: String, - val content: String, - val restaurantName: String, -) - -data class Restaurant( - val name: String, - val category: String, - val image: List, - val rating: Double, - val reviewCount: Int, - val loveCount: Int, -) - @HiltViewModel class HomeViewModel @Inject constructor( private val getHomeRestaurantUseCase: GetHomeRestaurantUseCase, - private val getUniversityIndexUseCase: GetUniversityIndexUseCase + private val getUniversityIndexUseCase: GetUniversityIndexUseCase, + private val getHomeReviewUseCase: GetHomeReviewUseCase ): BaseViewModel( HomeState() ) { @@ -46,6 +29,7 @@ class HomeViewModel @Inject constructor( when (event) { is HomeEvent.InitHomeScreen -> { getUnivRestaurant() + getHomeReview() } is HomeEvent.OnClickDetailList -> { sendEffect({ HomeEffect.NavigateToDetailListScreen(event.detailListScreenType) }) @@ -87,4 +71,25 @@ class HomeViewModel @Inject constructor( } } } + + private fun getHomeReview() { + viewModelScope.launch { + getHomeReviewUseCase( + offset = 0, + limit = 3, + order = "name", + group = null, + grade = null, + campusIdx = getUniversityIndexUseCase().first().toInt() + ).onSuccess { + updateState { + copy( + reviewData = it.data + ) + } + }.onFailure { + Log.d("gg1234", "gg") + } + } + } } 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 32464c8d..838e17b5 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 @@ -20,7 +20,7 @@ import com.everymeal.presentation.ui.bottom.navigateBottomNavigationScreen import com.everymeal.presentation.ui.detail.DetailListScreen import com.everymeal.presentation.ui.home.HomeScreen import com.everymeal.presentation.ui.mypage.MyPageScreen -import com.everymeal.presentation.ui.mypage.WithDrawScreen +import com.everymeal.presentation.ui.withdraw.WithDrawScreen import com.everymeal.presentation.ui.restaurant.DetailRestaurantScreen import com.everymeal.presentation.ui.review.search.ReviewSearchScreen import com.everymeal.presentation.ui.search.SearchScreen @@ -114,7 +114,11 @@ fun MainScreen( ReviewSearchScreen(navController = navController) } composable(route = EveryMealRoute.WITH_DRAW.route) { - WithDrawScreen() + WithDrawScreen( + onBackClick = { + navController.popBackStack() + } + ) } composable(route = EveryMealRoute.SEARCH.route) { SearchScreen(navController = navController) diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/mypage/MyPageScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/mypage/MyPageScreen.kt index 973edd00..3d596191 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/mypage/MyPageScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/mypage/MyPageScreen.kt @@ -36,6 +36,7 @@ import com.everymeal.presentation.ui.theme.Gray400 import com.everymeal.presentation.ui.theme.Gray600 import com.everymeal.presentation.ui.theme.Gray800 import com.everymeal.presentation.ui.theme.Gray900 +import com.everymeal.presentation.util.noRippleClickable @Composable @@ -231,7 +232,7 @@ fun MyTabMenu( modifier = modifier .fillMaxWidth() .padding(top = 24.dp) - .clickable(onClick = onClick) + .noRippleClickable(onClick = onClick) .padding(vertical = 3.dp) ) { Row ( diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/mypage/WithDrawScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/mypage/WithDrawScreen.kt deleted file mode 100644 index 113f70d1..00000000 --- a/presentation/src/main/java/com/everymeal/presentation/ui/mypage/WithDrawScreen.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.everymeal.presentation.ui.mypage - -import androidx.compose.runtime.Composable - -@Composable -fun WithDrawScreen() { - -} \ No newline at end of file diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/restaurant/DetailRestaurantScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/restaurant/DetailRestaurantScreen.kt index 7b7296bb..a93b79ab 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/restaurant/DetailRestaurantScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/restaurant/DetailRestaurantScreen.kt @@ -1,5 +1,10 @@ package com.everymeal.presentation.ui.restaurant +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkHorizontally +import androidx.compose.animation.slideInHorizontally import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -150,7 +155,11 @@ fun DetailRestaurantScreen( ) } } - if (viewState.isFabClicked) { + AnimatedVisibility( + visible = viewState.isFabClicked, + enter = fadeIn() + slideInHorizontally(), + exit = fadeOut() + shrinkHorizontally() + ) { Box( modifier = Modifier .fillMaxSize() diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/save/SaveScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/save/SaveScreen.kt index ffcf1e41..9351b863 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/save/SaveScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/save/SaveScreen.kt @@ -32,6 +32,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.everymeal.presentation.R import com.everymeal.presentation.ui.save.chip.ChipStyle import com.everymeal.presentation.ui.save.chip.Chips +import com.everymeal.presentation.ui.theme.EveryMealTypography import com.everymeal.presentation.ui.theme.Gray800 import com.everymeal.presentation.ui.theme.Grey2 import com.everymeal.presentation.ui.theme.Grey7 @@ -86,10 +87,7 @@ fun SaveTopBar( title = { Text( text = title, - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Bold - ) + style = EveryMealTypography.Subtitle2 ) }, navigationIcon = { diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/withdraw/WithDrawContract.kt b/presentation/src/main/java/com/everymeal/presentation/ui/withdraw/WithDrawContract.kt new file mode 100644 index 00000000..8a9d35a9 --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/ui/withdraw/WithDrawContract.kt @@ -0,0 +1,29 @@ +package com.everymeal.presentation.ui.withdraw + +import com.everymeal.presentation.base.LoadState +import com.everymeal.presentation.base.ViewEvent +import com.everymeal.presentation.base.ViewSideEffect +import com.everymeal.presentation.base.ViewState + +class WithDrawContract { + data class WithDrawState( + val uiState: LoadState = LoadState.LOADING, + val selectedReason: WithDrawReasonType? = null + ) : ViewState + + sealed class WithDrawEvent : ViewEvent { + data class WithDrawReasonSelected(val reason: WithDrawReasonType) : WithDrawEvent() + } + + sealed class WithDrawEffect : ViewSideEffect { + + } +} + +enum class WithDrawReasonType(val reason : String) { + NOT_USE("앱을 잘 쓰지 않아요"), + INCONVENIENT("사용성이 불편해요"), + ERROR("오류가 자주 발생해요"), + CHANGE_SCHOOL("학교가 바뀌었어요"), + ETC("기타") +} \ No newline at end of file diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/withdraw/WithDrawScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/withdraw/WithDrawScreen.kt new file mode 100644 index 00000000..4223e6cf --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/ui/withdraw/WithDrawScreen.kt @@ -0,0 +1,123 @@ +package com.everymeal.presentation.ui.withdraw + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.everymeal.presentation.R +import com.everymeal.presentation.ui.save.SaveTopBar +import com.everymeal.presentation.ui.theme.EveryMealTypography +import com.everymeal.presentation.ui.theme.Gray100 +import com.everymeal.presentation.ui.theme.Gray300 +import com.everymeal.presentation.ui.theme.Gray400 +import com.everymeal.presentation.ui.theme.Gray700 +import com.everymeal.presentation.ui.theme.Gray800 +import com.everymeal.presentation.ui.theme.Gray900 +import com.everymeal.presentation.ui.theme.Main100 +import com.everymeal.presentation.util.noRippleClickable + +@Composable +fun WithDrawScreen( + viewModel: WithDrawViewModel = hiltViewModel(), + onBackClick: () -> Unit = {} +) { + val viewState by viewModel.viewState.collectAsState() + + Scaffold( + topBar = { + SaveTopBar( + title = "탈퇴하기", + onBackClick = onBackClick + ) + } + ) { innerPadding -> + LazyColumn( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize() + .padding(horizontal = 20.dp) + ) { + item { + Spacer(modifier = Modifier.padding(top = 40.dp)) + Text( + text = "에브리밀을 정말 떠나시겠어요?", + style = EveryMealTypography.Heading1, + color = Gray900 + ) + Spacer(modifier = Modifier.padding(top = 6.dp)) + Text( + text = "탈퇴 이유를 알려주시면 큰 도움이 돼요", + style = EveryMealTypography.Body2, + color = Gray700 + ) + } + + item { + Spacer(modifier = Modifier.padding(top = 36.dp)) + WithDrawReason(viewModel, viewState, WithDrawReasonType.NOT_USE) + WithDrawReason(viewModel, viewState, WithDrawReasonType.INCONVENIENT) + WithDrawReason(viewModel, viewState, WithDrawReasonType.ERROR) + WithDrawReason(viewModel, viewState, WithDrawReasonType.CHANGE_SCHOOL) + WithDrawReason(viewModel, viewState, WithDrawReasonType.ETC) + } + } + } +} + +@Composable +fun WithDrawReason( + viewModel : WithDrawViewModel = hiltViewModel(), + viewState : WithDrawContract.WithDrawState, + withDrawReasonType : WithDrawReasonType, +) { + val isSelected = viewState.selectedReason == withDrawReasonType + + Row( + modifier = Modifier + .border(1.dp, if(isSelected) Main100 else Gray300, RoundedCornerShape(10.dp)) + .fillMaxWidth() + .noRippleClickable { + viewModel.setEvent(WithDrawContract.WithDrawEvent.WithDrawReasonSelected(withDrawReasonType)) + } + .padding(vertical = 18.dp, horizontal = 16.dp) + ) { + Text( + text = withDrawReasonType.reason, + style = EveryMealTypography.Subtitle3, + color = Gray800 + ) + Spacer(modifier = Modifier.weight(1f)) + Image( + imageVector = ImageVector.vectorResource(id = R.drawable.icon_check_circle_mono), + contentDescription = null, + colorFilter = if(isSelected) ColorFilter.tint(Main100) else ColorFilter.tint(Gray400), + ) + } + Spacer(modifier = Modifier.padding(bottom = 12.dp)) +} + +@Preview +@Composable +fun WithDrawScreenPreview() { + WithDrawScreen() +} \ No newline at end of file diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/withdraw/WithDrawViewModel.kt b/presentation/src/main/java/com/everymeal/presentation/ui/withdraw/WithDrawViewModel.kt new file mode 100644 index 00000000..fb3c9083 --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/ui/withdraw/WithDrawViewModel.kt @@ -0,0 +1,23 @@ +package com.everymeal.presentation.ui.withdraw + +import com.everymeal.presentation.base.BaseViewModel +import com.everymeal.presentation.ui.mypage.MyPageContract +import javax.inject.Inject + +class WithDrawViewModel @Inject constructor( + +) : BaseViewModel( + WithDrawContract.WithDrawState() +) { + override fun handleEvents(event: WithDrawContract.WithDrawEvent) { + when(event) { + is WithDrawContract.WithDrawEvent.WithDrawReasonSelected -> { + updateState { + copy( + selectedReason = event.reason + ) + } + } + } + } +} diff --git a/presentation/src/main/java/com/everymeal/presentation/util/Extension.kt b/presentation/src/main/java/com/everymeal/presentation/util/Extension.kt new file mode 100644 index 00000000..d6c6a666 --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/util/Extension.kt @@ -0,0 +1,19 @@ +package com.everymeal.presentation.util + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed + +fun Modifier.noRippleClickable( + enabled: Boolean = true, + onClick: () -> Unit = {}, +): Modifier = composed { + clickable( + enabled = enabled, + indication = null, + interactionSource = remember { MutableInteractionSource() }) { + onClick() + } +} \ No newline at end of file diff --git a/presentation/src/main/res/drawable/icon_check_circle_mono.xml b/presentation/src/main/res/drawable/icon_check_circle_mono.xml new file mode 100644 index 00000000..c6fa47de --- /dev/null +++ b/presentation/src/main/res/drawable/icon_check_circle_mono.xml @@ -0,0 +1,10 @@ + + +