diff --git a/app/src/main/java/com/everymeal/everymeal_android/di/NetworkModule.kt b/app/src/main/java/com/everymeal/everymeal_android/di/NetworkModule.kt index fecafbfa..20b48a78 100644 --- a/app/src/main/java/com/everymeal/everymeal_android/di/NetworkModule.kt +++ b/app/src/main/java/com/everymeal/everymeal_android/di/NetworkModule.kt @@ -3,6 +3,8 @@ package com.everymeal.everymeal_android.di import com.everymeal.data.service.auth.AuthApi import com.everymeal.data.service.onboarding.OnboardingApi import com.everymeal.data.service.restaurant.RestaurantApi +import com.everymeal.data.service.review.StoreReviewApi +import com.everymeal.data.service.search.SearchService import com.everymeal.everymeal_android.BuildConfig import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import dagger.Module @@ -30,20 +32,20 @@ object NetworkModule { fun provideClient(): OkHttpClient { val interceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) return OkHttpClient.Builder() - .addInterceptor(interceptor) - .connectTimeout(100, TimeUnit.SECONDS) - .readTimeout(100, TimeUnit.SECONDS) - .build() + .addInterceptor(interceptor) + .connectTimeout(100, TimeUnit.SECONDS) + .readTimeout(100, TimeUnit.SECONDS) + .build() } @Provides @Singleton fun provideRetrofit(client: OkHttpClient): Retrofit { return Retrofit.Builder() - .baseUrl(BASE_URL) - .addConverterFactory(json.asConverterFactory(contentType)) - .client(client) - .build() + .baseUrl(BASE_URL) + .addConverterFactory(json.asConverterFactory(contentType)) + .client(client) + .build() } @Provides @@ -63,4 +65,16 @@ object NetworkModule { fun provideRestaurantApi(retrofit: Retrofit): RestaurantApi { return retrofit.create(RestaurantApi::class.java) } -} \ No newline at end of file + + @Provides + @Singleton + fun provideReviewApi(retrofit: Retrofit): StoreReviewApi { + return retrofit.create(StoreReviewApi::class.java) + } + + @Provides + @Singleton + fun provideSearchApi(retrofit: Retrofit): SearchService { + return retrofit.create(SearchService::class.java) + } +} diff --git a/app/src/main/java/com/everymeal/everymeal_android/di/RepositoryModule.kt b/app/src/main/java/com/everymeal/everymeal_android/di/RepositoryModule.kt index 9121c1b9..148bdf85 100644 --- a/app/src/main/java/com/everymeal/everymeal_android/di/RepositoryModule.kt +++ b/app/src/main/java/com/everymeal/everymeal_android/di/RepositoryModule.kt @@ -8,15 +8,20 @@ import com.everymeal.data.datasource.onboarding.OnboardingDataSource import com.everymeal.data.datasource.onboarding.OnboardingDataSourceImpl import com.everymeal.data.datasource.restaurant.RestaurantDataSource import com.everymeal.data.datasource.restaurant.RestaurantDataSourceImpl +import com.everymeal.data.datasource.review.ReviewDataSource +import com.everymeal.data.datasource.review.ReviewDataSourceImpl +import com.everymeal.data.repository.auth.DefaultAuthRepository import com.everymeal.data.repository.local.LocalRepositoryImpl -import com.everymeal.data.repository.DefaultAuthRepository import com.everymeal.data.repository.onboarding.OnboardingRepositoryImpl import com.everymeal.data.repository.restaurant.RestaurantRepositoryImpl -import com.everymeal.domain.repository.local.LocalRepository +import com.everymeal.data.repository.review.DefaultReviewRepository +import com.everymeal.data.repository.search.DefaultSearchRepository import com.everymeal.domain.repository.auth.AuthRepository +import com.everymeal.domain.repository.local.LocalRepository import com.everymeal.domain.repository.onboarding.OnboardingRepository import com.everymeal.domain.repository.restaurant.RestaurantRepository -import com.everymeal.presentation.ui.home.Restaurant +import com.everymeal.domain.repository.review.ReviewRepository +import com.everymeal.domain.repository.search.SearchRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -30,48 +35,66 @@ abstract class RepositoryModule { @Singleton @Binds abstract fun bindOnboardingRepository( - onboardingRepositoryImpl: OnboardingRepositoryImpl + onboardingRepositoryImpl: OnboardingRepositoryImpl, ): OnboardingRepository @Singleton @Binds abstract fun bindOnboardingDataSource( - onboardingDataSourceImpl: OnboardingDataSourceImpl + onboardingDataSourceImpl: OnboardingDataSourceImpl, ): OnboardingDataSource @Singleton @Binds abstract fun bindLocalRepository( - localRepositoryImpl: LocalRepositoryImpl + localRepositoryImpl: LocalRepositoryImpl, ): LocalRepository @Singleton @Binds abstract fun bindLocalDataSource( - localDataSourceImpl: LocalDataSourceImpl + localDataSourceImpl: LocalDataSourceImpl, ): LocalDataSource @Singleton @Binds abstract fun bindAuthRemoteDataSource( - authRemoteDataSourceImpl: AuthRemoteRemoteDataSourceImpl + authRemoteDataSourceImpl: AuthRemoteRemoteDataSourceImpl, ): AuthRemoteDataSource + @Singleton + @Binds + abstract fun bindRestaurantDataSource( + restaurantDataSourceImpl: RestaurantDataSourceImpl, + ): RestaurantDataSource + + @Singleton + @Binds + abstract fun bindRestaurantRepository( + restaurantRepositoryImpl: RestaurantRepositoryImpl, + ): RestaurantRepository + @Singleton @Binds abstract fun bindAuthRepository( - defaultAuthRepository: DefaultAuthRepository + defaultAuthRepository: DefaultAuthRepository, ): AuthRepository @Singleton @Binds - abstract fun bindRestaurantDataSource( - restaurantDataSourceImpl: RestaurantDataSourceImpl - ): RestaurantDataSource + abstract fun bindReviewDataSource( + reviewDataSourceImpl: ReviewDataSourceImpl, + ): ReviewDataSource @Singleton @Binds - abstract fun bindRestaurantRepository( - restaurantRepositoryImpl: RestaurantRepositoryImpl - ): RestaurantRepository + abstract fun bindReviewRepository( + defaultReviewRepository: DefaultReviewRepository, + ): ReviewRepository + + @Singleton + @Binds + abstract fun bindSearchRepository( + defaultSearchRepository: DefaultSearchRepository, + ): SearchRepository } diff --git a/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteRemoteDataSourceImpl.kt b/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteRemoteDataSourceImpl.kt index 3be62dab..e0e5ea24 100644 --- a/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteRemoteDataSourceImpl.kt +++ b/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteRemoteDataSourceImpl.kt @@ -1,17 +1,14 @@ package com.everymeal.data.datasource.auth -import com.everymeal.data.model.auth.EmailResponse -import com.everymeal.data.model.auth.toEmail import com.everymeal.data.model.auth.toEmailAuthToken import com.everymeal.data.model.auth.toEmailRequest -import com.everymeal.data.model.unwrapData import com.everymeal.data.service.auth.AuthApi import com.everymeal.domain.model.auth.Email import com.everymeal.domain.model.auth.EmailAuthToken import javax.inject.Inject class AuthRemoteRemoteDataSourceImpl @Inject constructor( - private val authApi: AuthApi + private val authApi: AuthApi, ) : AuthRemoteDataSource { override suspend fun postEmail(email: Email): Result = runCatching { 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 new file mode 100644 index 00000000..327428b2 --- /dev/null +++ b/data/src/main/java/com/everymeal/data/datasource/review/ReviewDataSource.kt @@ -0,0 +1,14 @@ +package com.everymeal.data.datasource.review + +import com.everymeal.data.model.review.ReviewListResponse +import com.everymeal.data.model.review.StoreReviewRequest + +interface ReviewDataSource { + suspend fun getReviewList( + cursorIdx: Int, + mealIdx: Int, + pageSize: 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 new file mode 100644 index 00000000..3aca7eb3 --- /dev/null +++ b/data/src/main/java/com/everymeal/data/datasource/review/ReviewDataSourceImpl.kt @@ -0,0 +1,29 @@ +package com.everymeal.data.datasource.review + +import com.everymeal.data.model.review.ReviewListResponse +import com.everymeal.data.model.review.StoreReviewRequest +import com.everymeal.data.model.unwrapRunCatching +import com.everymeal.data.service.review.StoreReviewApi +import javax.inject.Inject + +class ReviewDataSourceImpl @Inject constructor( + private val storeReviewApi: StoreReviewApi, +) : ReviewDataSource { + + override suspend fun getReviewList( + cursorIdx: Int, + mealIdx: Int, + pageSize: Int, + ): Result = + unwrapRunCatching { + storeReviewApi.getStoreReviewsWithId( + cursorIdx, + mealIdx, + pageSize, + ) + } + + override suspend fun postReview( + storeReviewRequest: StoreReviewRequest, + ): Result = unwrapRunCatching { storeReviewApi.postStoreReview(storeReviewRequest) } +} diff --git a/data/src/main/java/com/everymeal/data/model/BaseResponse.kt b/data/src/main/java/com/everymeal/data/model/BaseResponse.kt index ba187ad5..41366b92 100644 --- a/data/src/main/java/com/everymeal/data/model/BaseResponse.kt +++ b/data/src/main/java/com/everymeal/data/model/BaseResponse.kt @@ -11,4 +11,8 @@ data class BaseResponse( fun Result>.unwrapData(): Result { return this.map { it.data } -} \ No newline at end of file +} + +suspend fun unwrapRunCatching(block: suspend () -> BaseResponse): Result { + return runCatching { block() }.unwrapData() +} diff --git a/data/src/main/java/com/everymeal/data/model/restaruant/GetUnivRestaurantResponse.kt b/data/src/main/java/com/everymeal/data/model/restaruant/GetUnivRestaurantResponse.kt index df11aa84..1beecb68 100644 --- a/data/src/main/java/com/everymeal/data/model/restaruant/GetUnivRestaurantResponse.kt +++ b/data/src/main/java/com/everymeal/data/model/restaruant/GetUnivRestaurantResponse.kt @@ -16,7 +16,7 @@ data class GetUnivRestaurantResponse( val sort: Sort, val numberOfElements: Int, val first: Boolean, - val empty: Boolean + val empty: Boolean, ) @Serializable @@ -31,7 +31,7 @@ data class RestaurantResponse( val reviewCount: Int, val recommendedCount: Int, val images: List?, - val isLiked: Boolean + val isLiked: Boolean, ) @Serializable @@ -41,17 +41,17 @@ data class Pageable( val pageNumber: Int, val pageSize: Int, val paged: Boolean, - val unpaged: Boolean + val unpaged: Boolean, ) @Serializable data class Sort( val empty: Boolean, val sorted: Boolean, - val unsorted: Boolean + val unsorted: Boolean, ) -fun RestaurantResponse.toEntity(): RestaurantDataEntity { +fun RestaurantResponse.toRestaurant(): RestaurantDataEntity { return RestaurantDataEntity( idx = this.idx, name = this.name, @@ -63,12 +63,13 @@ fun RestaurantResponse.toEntity(): RestaurantDataEntity { reviewCount = this.reviewCount, recommendedCount = this.recommendedCount, images = this.images, - isLiked = this.isLiked + isLiked = this.isLiked, ) } -fun GetUnivRestaurantResponse.toEntity(): GetUnivRestaurantEntity { + +fun GetUnivRestaurantResponse.toGetUnivRestaurantEntity(): GetUnivRestaurantEntity { return GetUnivRestaurantEntity( - data = this.content.map { it.toEntity() } + data = this.content.map { it.toRestaurant() } ) -} \ No newline at end of file +} diff --git a/data/src/main/java/com/everymeal/data/model/restaruant/SearchRestaurantResponse.kt b/data/src/main/java/com/everymeal/data/model/restaruant/SearchRestaurantResponse.kt new file mode 100644 index 00000000..0c8fb431 --- /dev/null +++ b/data/src/main/java/com/everymeal/data/model/restaruant/SearchRestaurantResponse.kt @@ -0,0 +1,125 @@ +package com.everymeal.data.model.restaruant + +import com.everymeal.domain.model.restaurant.RestaurantDataEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SearchRestaurantResponse( + @SerialName("data") + val `data`: Data? = null, + @SerialName("localDateTime") + val localDateTime: String? = null, + @SerialName("message") + val message: String? = null, +) { + @Serializable + data class Data( + @SerialName("content") + val content: List? = null, + @SerialName("empty") + val empty: Boolean? = null, + @SerialName("first") + val first: Boolean? = null, + @SerialName("last") + val last: Boolean? = null, + @SerialName("number") + val number: Int? = null, + @SerialName("numberOfElements") + val numberOfElements: Int? = null, + @SerialName("pageable") + val pageable: Pageable? = null, + @SerialName("size") + val size: Int? = null, + @SerialName("sort") + val sort: Sort? = null, + @SerialName("totalElements") + val totalElements: Int? = null, + @SerialName("totalPages") + val totalPages: Int? = null, + ) { + @Serializable + data class Content( + @SerialName("address") + val address: String? = null, + @SerialName("categoryDetail") + val categoryDetail: String? = null, + @SerialName("distance") + val distance: Int? = null, + @SerialName("grade") + val grade: Int? = null, + @SerialName("idx") + val idx: Int? = null, + @SerialName("images") + val images: List? = null, + @SerialName("isLiked") + val isLiked: Boolean? = null, + @SerialName("name") + val name: String? = null, + @SerialName("phoneNumber") + val phoneNumber: String? = null, + @SerialName("recommendedCount") + val recommendedCount: Int? = null, + @SerialName("reviewCount") + val reviewCount: Int? = null, + ) + + @Serializable + data class Pageable( + @SerialName("offset") + val offset: Int? = null, + @SerialName("pageNumber") + val pageNumber: Int? = null, + @SerialName("pageSize") + val pageSize: Int? = null, + @SerialName("paged") + val paged: Boolean? = null, + @SerialName("sort") + val sort: Sort? = null, + @SerialName("unpaged") + val unpaged: Boolean? = null, + ) { + @Serializable + data class Sort( + @SerialName("empty") + val empty: Boolean? = null, + @SerialName("sorted") + val sorted: Boolean? = null, + @SerialName("unsorted") + val unsorted: Boolean? = null, + ) + } + + @Serializable + data class Sort( + @SerialName("empty") + val empty: Boolean? = null, + @SerialName("sorted") + val sorted: Boolean? = null, + @SerialName("unsorted") + val unsorted: Boolean? = null, + ) + } +} + + +fun SearchRestaurantResponse.toRestaurants(): List { + return this.data?.content?.mapNotNull { content -> + content?.let { + RestaurantDataEntity( + idx = it.idx ?: 0, + name = it.name.orEmpty(), + address = it.address.orEmpty(), + phoneNumber = it.phoneNumber.orEmpty(), + categoryDetail = it.categoryDetail.orEmpty(), + distance = it.distance ?: 0, + grade = it.grade?.toFloat() + ?: 0f, + reviewCount = it.reviewCount ?: 0, + recommendedCount = it.recommendedCount ?: 0, + images = it.images?.filterNotNull(), + isLiked = it.isLiked ?: false, + ) + } + } ?: emptyList() +} 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 new file mode 100644 index 00000000..e4861abe --- /dev/null +++ b/data/src/main/java/com/everymeal/data/model/review/ReviewListResponse.kt @@ -0,0 +1,54 @@ +package com.everymeal.data.model.review + + +import com.everymeal.domain.model.review.Review +import kotlinx.serialization.SerialName +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 + ) + } +} diff --git a/data/src/main/java/com/everymeal/data/model/review/StoreReviewRequest.kt b/data/src/main/java/com/everymeal/data/model/review/StoreReviewRequest.kt new file mode 100644 index 00000000..4e015381 --- /dev/null +++ b/data/src/main/java/com/everymeal/data/model/review/StoreReviewRequest.kt @@ -0,0 +1,35 @@ +package com.everymeal.data.model.review + +import com.everymeal.domain.model.review.UserReview +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class StoreReviewRequest( + @SerialName("idx") + val idx: Int, + @SerialName("grade") + val grade: Int, + @SerialName("content") + val content: String, + @SerialName("imageList") + val imageList: List, +) { + fun toUserReview(): UserReview { + return UserReview( + idx = idx, + grade = grade, + content = content, + imageList = imageList, + ) + } +} + +fun UserReview.toReviewRequest(): StoreReviewRequest { + return StoreReviewRequest( + idx = idx, + grade = grade, + content = content, + imageList = imageList, + ) +} diff --git a/data/src/main/java/com/everymeal/data/repository/DefaultAuthRepository.kt b/data/src/main/java/com/everymeal/data/repository/auth/DefaultAuthRepository.kt similarity index 82% rename from data/src/main/java/com/everymeal/data/repository/DefaultAuthRepository.kt rename to data/src/main/java/com/everymeal/data/repository/auth/DefaultAuthRepository.kt index ebed66e4..43c8c9eb 100644 --- a/data/src/main/java/com/everymeal/data/repository/DefaultAuthRepository.kt +++ b/data/src/main/java/com/everymeal/data/repository/auth/DefaultAuthRepository.kt @@ -1,4 +1,4 @@ -package com.everymeal.data.repository +package com.everymeal.data.repository.auth import com.everymeal.data.datasource.auth.AuthRemoteDataSource import com.everymeal.domain.model.auth.Email @@ -7,7 +7,7 @@ import com.everymeal.domain.repository.auth.AuthRepository import javax.inject.Inject class DefaultAuthRepository @Inject constructor( - private val authRemoteDataSource: AuthRemoteDataSource + private val authRemoteDataSource: AuthRemoteDataSource, ) : AuthRepository { override suspend fun postEmail(email: Email): Result { return authRemoteDataSource.postEmail(email) @@ -15,7 +15,7 @@ class DefaultAuthRepository @Inject constructor( override suspend fun verifyToken( emailAuthToken: String, - emailAuthValue: String + emailAuthValue: String, ): Result { return authRemoteDataSource.verifyToken(emailAuthToken, emailAuthValue) } diff --git a/data/src/main/java/com/everymeal/data/repository/restaurant/RestaurantRepositoryImpl.kt b/data/src/main/java/com/everymeal/data/repository/restaurant/RestaurantRepositoryImpl.kt index 85c5e436..87cbc778 100644 --- a/data/src/main/java/com/everymeal/data/repository/restaurant/RestaurantRepositoryImpl.kt +++ b/data/src/main/java/com/everymeal/data/repository/restaurant/RestaurantRepositoryImpl.kt @@ -1,14 +1,15 @@ package com.everymeal.data.repository.restaurant import androidx.paging.PagingData +import androidx.paging.map import com.everymeal.data.datasource.restaurant.RestaurantDataSource +import com.everymeal.data.model.restaruant.toGetUnivRestaurantEntity +import com.everymeal.data.model.restaruant.toRestaurant +import com.everymeal.domain.model.restaurant.GetUnivRestaurantEntity import com.everymeal.domain.model.restaurant.RestaurantDataEntity import com.everymeal.domain.repository.restaurant.RestaurantRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import androidx.paging.map -import com.everymeal.data.model.restaruant.toEntity -import com.everymeal.domain.model.restaurant.GetUnivRestaurantEntity import javax.inject.Inject class RestaurantRepositoryImpl @Inject constructor( @@ -19,15 +20,15 @@ class RestaurantRepositoryImpl @Inject constructor( order: String, group: String?, grade: String? - ) : Flow> { + ): Flow> { return restaurantDataSource.getUnivRestaurant(campusIdx, order, group, grade) .map { pagingData -> - pagingData.map { it.toEntity() } + pagingData.map { it.toRestaurant() } } } override suspend fun getRestaurantDetail(index: Int): Result { - return restaurantDataSource.getRestaurantDetail(index).map { it.toEntity() } + return restaurantDataSource.getRestaurantDetail(index).map { it.toRestaurant() } } override suspend fun getHomeRestaurant( @@ -36,6 +37,7 @@ class RestaurantRepositoryImpl @Inject constructor( group: String?, grade: String? ): Result { - return restaurantDataSource.getHomeRestaurant(campusIdx, order, group, grade).map { it.toEntity() } + return restaurantDataSource.getHomeRestaurant(campusIdx, order, group, grade) + .map { it.toGetUnivRestaurantEntity() } } -} \ No newline at end of file +} 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 new file mode 100644 index 00000000..6abb844c --- /dev/null +++ b/data/src/main/java/com/everymeal/data/repository/review/DefaultReviewRepository.kt @@ -0,0 +1,29 @@ +package com.everymeal.data.repository.review + +import com.everymeal.data.datasource.review.ReviewDataSource +import com.everymeal.data.model.review.toReviewRequest +import com.everymeal.domain.model.review.Review +import com.everymeal.domain.model.review.UserReview +import com.everymeal.domain.repository.review.ReviewRepository +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 postReview( + userReview: UserReview, + ): Result { + return reviewDataSource.postReview( + userReview.toReviewRequest(), + ) + } +} diff --git a/data/src/main/java/com/everymeal/data/repository/search/DefaultSearchRepository.kt b/data/src/main/java/com/everymeal/data/repository/search/DefaultSearchRepository.kt new file mode 100644 index 00000000..926818ed --- /dev/null +++ b/data/src/main/java/com/everymeal/data/repository/search/DefaultSearchRepository.kt @@ -0,0 +1,17 @@ +package com.everymeal.data.repository.search + +import com.everymeal.data.model.restaruant.toRestaurants +import com.everymeal.data.service.search.SearchService +import com.everymeal.domain.model.restaurant.RestaurantDataEntity +import com.everymeal.domain.repository.search.SearchRepository +import javax.inject.Inject + +class DefaultSearchRepository @Inject constructor( + private val searchService: SearchService, +) : SearchRepository { + override suspend fun search(keyword: String): Result> { + return runCatching { + searchService.search(keyword).toRestaurants() + } + } +} 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 new file mode 100644 index 00000000..06100a94 --- /dev/null +++ b/data/src/main/java/com/everymeal/data/service/review/StoreReviewApi.kt @@ -0,0 +1,32 @@ +package com.everymeal.data.service.review + +import com.everymeal.data.model.BaseResponse +import com.everymeal.data.model.review.ReviewListResponse +import com.everymeal.data.model.review.StoreReviewRequest +import retrofit2.http.Body +import retrofit2.http.GET +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/reviews") + suspend fun getStoresReviews( + @Query("offset") offset: Int, + @Query("limit") limit: Int, + @Query("order") order: String, + @Query("group") group: String, + @Query("grade") grade: Int, + ): BaseResponse + + @POST("/api/v1/reviews/store") + suspend fun postStoreReview( + @Body storeReviewRequest: StoreReviewRequest, + ): BaseResponse +} diff --git a/data/src/main/java/com/everymeal/data/service/search/SearchService.kt b/data/src/main/java/com/everymeal/data/service/search/SearchService.kt new file mode 100644 index 00000000..ef02afa8 --- /dev/null +++ b/data/src/main/java/com/everymeal/data/service/search/SearchService.kt @@ -0,0 +1,13 @@ +package com.everymeal.data.service.search + +import com.everymeal.data.model.restaruant.SearchRestaurantResponse +import retrofit2.http.GET +import retrofit2.http.Path + +interface SearchService { + // TODO 임시 캠퍼스 ID + @GET("/api/v1/stores/{0}/{keyword}") + suspend fun search( + @Path("keyword") keyword: String, + ): SearchRestaurantResponse +} diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index e3f42c70..e8a5b540 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -15,5 +15,6 @@ dependencies { // Coroutines implementation(libs.kotlin.coroutines) - implementation("androidx.paging:paging-common:3.2.0-rc01") + implementation(libs.androidx.paging.common.v320rc01) + testImplementation(libs.junit.jupiter) } diff --git a/domain/src/main/java/com/everymeal/domain/model/restaurant/GetUnivRestaurantEntity.kt b/domain/src/main/java/com/everymeal/domain/model/restaurant/GetUnivRestaurantEntity.kt index 0ef6ab29..904a020e 100644 --- a/domain/src/main/java/com/everymeal/domain/model/restaurant/GetUnivRestaurantEntity.kt +++ b/domain/src/main/java/com/everymeal/domain/model/restaurant/GetUnivRestaurantEntity.kt @@ -3,18 +3,3 @@ package com.everymeal.domain.model.restaurant data class GetUnivRestaurantEntity( val data: List ) - -data class RestaurantDataEntity( - 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 -) - diff --git a/domain/src/main/java/com/everymeal/domain/model/restaurant/RestaurantDataEntity.kt b/domain/src/main/java/com/everymeal/domain/model/restaurant/RestaurantDataEntity.kt new file mode 100644 index 00000000..00353e5c --- /dev/null +++ b/domain/src/main/java/com/everymeal/domain/model/restaurant/RestaurantDataEntity.kt @@ -0,0 +1,16 @@ +package com.everymeal.domain.model.restaurant + +data class RestaurantDataEntity( + 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 +) + diff --git a/domain/src/main/java/com/everymeal/domain/model/review/ReviewDetail.kt b/domain/src/main/java/com/everymeal/domain/model/review/ReviewDetail.kt new file mode 100644 index 00000000..86483247 --- /dev/null +++ b/domain/src/main/java/com/everymeal/domain/model/review/ReviewDetail.kt @@ -0,0 +1,18 @@ +package com.everymeal.domain.model.review + +data class Review( + val reviewPagingList: List, + val reviewTotalCnt: Int +) { + data class ReviewDetail( + val content: String, + val grade: Int, + val imageList: List, + val mealCategory: String, + val mealType: String, + val restaurantName: String, + val reviewIdx: Int, + val reviewMarksCnt: Int + ) +} + diff --git a/domain/src/main/java/com/everymeal/domain/model/review/UserReview.kt b/domain/src/main/java/com/everymeal/domain/model/review/UserReview.kt new file mode 100644 index 00000000..994e2733 --- /dev/null +++ b/domain/src/main/java/com/everymeal/domain/model/review/UserReview.kt @@ -0,0 +1,8 @@ +package com.everymeal.domain.model.review + +data class UserReview( + val idx: Int, + val grade: Int, + val content: String, + val imageList: List +) diff --git a/domain/src/main/java/com/everymeal/domain/repository/restaurant/RestaurantRepository.kt b/domain/src/main/java/com/everymeal/domain/repository/restaurant/RestaurantRepository.kt index a91d4504..5d2deec5 100644 --- a/domain/src/main/java/com/everymeal/domain/repository/restaurant/RestaurantRepository.kt +++ b/domain/src/main/java/com/everymeal/domain/repository/restaurant/RestaurantRepository.kt @@ -12,16 +12,16 @@ interface RestaurantRepository { order: String, group: String? = null, grade: String? = null, - ) : Flow> + ): Flow> suspend fun getRestaurantDetail( index: Int - ) : Result + ): Result suspend fun getHomeRestaurant( campusIdx: Int, order: String, group: String? = null, grade: String? = null, - ) : Result -} \ No newline at end of file + ): Result +} 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 new file mode 100644 index 00000000..d999da28 --- /dev/null +++ b/domain/src/main/java/com/everymeal/domain/repository/review/ReviewRepository.kt @@ -0,0 +1,9 @@ +package com.everymeal.domain.repository.review + +import com.everymeal.domain.model.review.Review +import com.everymeal.domain.model.review.UserReview + +interface ReviewRepository { + suspend fun getReviewList(cursorIdx: Int, mealIdx: Int, pageSize: Int): Result + suspend fun postReview(userReview: UserReview): Result +} diff --git a/domain/src/main/java/com/everymeal/domain/repository/search/SearchRepository.kt b/domain/src/main/java/com/everymeal/domain/repository/search/SearchRepository.kt new file mode 100644 index 00000000..50f79e49 --- /dev/null +++ b/domain/src/main/java/com/everymeal/domain/repository/search/SearchRepository.kt @@ -0,0 +1,7 @@ +package com.everymeal.domain.repository.search + +import com.everymeal.domain.model.restaurant.RestaurantDataEntity + +interface SearchRepository { + suspend fun search(keyword: String): Result> +} diff --git a/domain/src/main/java/com/everymeal/domain/usecase/restaurant/GetDetailRestaurantUseCase.kt b/domain/src/main/java/com/everymeal/domain/usecase/restaurant/GetDetailRestaurantUseCase.kt index a6536bde..e455acd3 100644 --- a/domain/src/main/java/com/everymeal/domain/usecase/restaurant/GetDetailRestaurantUseCase.kt +++ b/domain/src/main/java/com/everymeal/domain/usecase/restaurant/GetDetailRestaurantUseCase.kt @@ -9,7 +9,7 @@ class GetDetailRestaurantUseCase @Inject constructor( ) { suspend operator fun invoke( restaurantIdx: Int, - ) : Result { + ): Result { return restaurantRepository.getRestaurantDetail(restaurantIdx) } -} \ No newline at end of file +} diff --git a/domain/src/main/java/com/everymeal/domain/usecase/restaurant/GetUnivRestaurantUseCase.kt b/domain/src/main/java/com/everymeal/domain/usecase/restaurant/GetUnivRestaurantUseCase.kt index 4257fb08..42d99f72 100644 --- a/domain/src/main/java/com/everymeal/domain/usecase/restaurant/GetUnivRestaurantUseCase.kt +++ b/domain/src/main/java/com/everymeal/domain/usecase/restaurant/GetUnivRestaurantUseCase.kt @@ -17,4 +17,4 @@ class GetUnivRestaurantUseCase @Inject constructor( ) : Flow> { return restaurantRepository.getUnivRestaurant(campusIdx, order, group, grade) } -} \ No newline at end of file +} 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 new file mode 100644 index 00000000..4584e5a5 --- /dev/null +++ b/domain/src/main/java/com/everymeal/domain/usecase/review/PostReviewUseCase.kt @@ -0,0 +1,13 @@ +package com.everymeal.domain.usecase.review + +import com.everymeal.domain.model.review.UserReview +import com.everymeal.domain.repository.review.ReviewRepository +import javax.inject.Inject + +class PostReviewUseCase @Inject constructor( + private val reviewRepository: ReviewRepository +) { + suspend operator fun invoke(userReview: UserReview) { + reviewRepository.postReview(userReview) + } +} diff --git a/domain/src/test/java/com/everymeal/domain/ExampleUnitTest.kt b/domain/src/test/java/com/everymeal/domain/ExampleUnitTest.kt deleted file mode 100644 index 33996285..00000000 --- a/domain/src/test/java/com/everymeal/domain/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.everymeal.domain - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f788ebfa..87c0b7be 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,7 @@ [versions] agp = "8.1.1" coil-compose = "2.4.0" +junit-jupiter = "5.8.1" kotlin = "1.8.21" core-ktx = "1.12.0" junit = "4.13.2" @@ -12,6 +13,7 @@ compose-bom = "2023.10.01" dagger-hilt = "2.46.1" hilt-compose = "1.0.0" okhttp = "4.11.0" +paging-common = "3.2.0-rc01" retrofit = "2.9.0" serialization = "1.6.0" kotlin-serilization = "1.0.0" @@ -27,11 +29,13 @@ paging-runtime = "3.1.0" [libraries] agp = { module = "com.android.tools.build:gradle", version.ref = "agp" } +androidx-paging-common-v320rc01 = { module = "androidx.paging:paging-common", version.ref = "paging-common" } coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil-compose" } core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" } activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity" } activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } diff --git a/presentation/src/main/java/com/everymeal/presentation/components/EveryMealRestaurantItem.kt b/presentation/src/main/java/com/everymeal/presentation/components/EveryMealRestaurantItem.kt index e97addd4..ed08ed75 100644 --- a/presentation/src/main/java/com/everymeal/presentation/components/EveryMealRestaurantItem.kt +++ b/presentation/src/main/java/com/everymeal/presentation/components/EveryMealRestaurantItem.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -33,7 +32,6 @@ import androidx.compose.ui.unit.sp import coil.compose.AsyncImage import com.everymeal.domain.model.restaurant.RestaurantDataEntity import com.everymeal.presentation.R -import com.everymeal.presentation.ui.home.HomeScreen import com.everymeal.presentation.ui.theme.EveryMeal_AndroidTheme import com.everymeal.presentation.ui.theme.Gray300 import com.everymeal.presentation.ui.theme.Gray500 @@ -51,12 +49,12 @@ fun EveryMealRestaurantItem( .fillMaxSize() .clickable( indication = null, - interactionSource = remember { MutableInteractionSource() } + interactionSource = remember { MutableInteractionSource() }, ) { onDetailClick(restaurant.idx) } .padding(horizontal = 20.dp) - .background(color = Color.White) + .background(color = Color.White), ) { RestaurantTitle(Modifier.fillMaxWidth(), restaurant) { onLoveClick() @@ -83,7 +81,7 @@ fun RestaurantTitle( color = Color.Black, fontSize = 17.sp, fontWeight = FontWeight.SemiBold, - maxLines = 1 + maxLines = 1, ) Text( modifier = Modifier @@ -93,7 +91,7 @@ fun RestaurantTitle( .padding(vertical = 3.dp, horizontal = 6.dp), text = restaurant.categoryDetail, color = Gray600, - fontSize = 12.sp + fontSize = 12.sp, ) Spacer(modifier = Modifier.weight(1f)) RestaurantLoveCount(restaurant, onLoveClick) @@ -110,7 +108,7 @@ fun RestaurantLoveCount( .padding(top = 6.dp) .clickable( indication = null, - interactionSource = remember { MutableInteractionSource() } + interactionSource = remember { MutableInteractionSource() }, ) { onLoveClick() }, horizontalAlignment = Alignment.CenterHorizontally, ) { @@ -216,7 +214,7 @@ fun RestaurantImage(restaurant: RestaurantDataEntity) { .aspectRatio(1f) .clip(RoundedCornerShape(8.dp)), model = restaurant.images!![0], - contentDescription = null + contentDescription = null, ) Spacer(modifier = Modifier .weight(2f) @@ -279,9 +277,5 @@ fun RestaurantImage(restaurant: RestaurantDataEntity) { @Composable fun HomeScreenPreview() { EveryMeal_AndroidTheme { - HomeScreen( - onDetailScreenClickType = {}, - onDetailRestaurantClick = {}, - ) } } diff --git a/presentation/src/main/java/com/everymeal/presentation/components/EveryMealTextFiled.kt b/presentation/src/main/java/com/everymeal/presentation/components/EveryMealTextFiled.kt index 20ffe883..34b19a2d 100644 --- a/presentation/src/main/java/com/everymeal/presentation/components/EveryMealTextFiled.kt +++ b/presentation/src/main/java/com/everymeal/presentation/components/EveryMealTextFiled.kt @@ -1,6 +1,8 @@ package com.everymeal.presentation.components import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults @@ -9,6 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.everymeal.presentation.ui.theme.Gray300 @@ -23,6 +26,8 @@ fun EveryMealTextField( isError: Boolean = false, supportingText: (@Composable () -> Unit)? = null, leadingIcon: (@Composable () -> Unit)? = null, + maxLines: Int = Int.MAX_VALUE, + onEnterPressed: (() -> Unit)? = null, // 'onSearch' 대신 'onEnterPressed' 사용 otherCustomization: (@Composable () -> Unit)? = null ) { TextField( @@ -42,6 +47,15 @@ fun EveryMealTextField( ) ) }, + maxLines = maxLines, + keyboardOptions = KeyboardOptions.Default.copy( + imeAction = if (onEnterPressed != null) ImeAction.Search else ImeAction.Default + ), + keyboardActions = KeyboardActions( + onSearch = { + onEnterPressed?.invoke() + } + ), shape = RoundedCornerShape(12.dp), colors = TextFieldDefaults.colors( focusedContainerColor = Gray300, @@ -49,7 +63,7 @@ fun EveryMealTextField( focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, disabledIndicatorColor = Color.Transparent - ), + ) ) otherCustomization?.invoke() } 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 340c31cc..3777d0ba 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 @@ -12,32 +12,34 @@ enum class BottomNavigation( HOME( route = EveryMealRoute.HOME.route, icon = R.drawable.icon_store, - title = R.string.bottom_nav_home + title = R.string.bottom_nav_home, ), UNIV_FOOD( route = EveryMealRoute.UNIV_FOOD.route, icon = R.drawable.icon_folk, - title = R.string.bottom_nav_univ_food + title = R.string.bottom_nav_univ_food, ), WHAT_FOOD( route = EveryMealRoute.WHAT_FOOD.route, icon = R.drawable.icon_chat_bubble, - title = R.string.bottom_nav_what_food + title = R.string.bottom_nav_what_food, ), MY_PAGE( route = EveryMealRoute.MY_PAGE.route, icon = R.drawable.icon_user, - title = R.string.bottom_nav_my_tab - ) + title = R.string.bottom_nav_my_tab, + ), } enum class EveryMealRoute(val route: String) { HOME("home"), + SEARCH("search"), UNIV_FOOD("univ-food"), WHAT_FOOD("what-food"), MY_PAGE("my-page"), DETAIL_LIST("detail-list"), DETAIL_RESTAURANT("detail-restaurant"), SCHOOL_AUTH("school-auth"), - WITH_DRAW("with-draw") + WITH_DRAW("with-draw"), + REVIEW_SEARCH("review-search"), } 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 9387613a..53776f89 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 @@ -1,10 +1,8 @@ package com.everymeal.presentation.ui.detail import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -22,7 +20,6 @@ import androidx.compose.runtime.getValue 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.graphics.ColorFilter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource @@ -41,12 +38,10 @@ import com.everymeal.presentation.components.EveryMealReportBottomSheetDialog import com.everymeal.presentation.components.EveryMealRestaurantItem import com.everymeal.presentation.components.EveryMealSortCategoryBottomSheetDialog import com.everymeal.presentation.ui.save.SaveTopBar -import com.everymeal.presentation.ui.signup.UnivSelectContract import com.everymeal.presentation.ui.theme.Grey2 import com.everymeal.presentation.ui.theme.Grey7 import com.everymeal.presentation.ui.theme.Main100 import com.everymeal.presentation.ui.theme.SubMain100 -import com.everymeal.presentation.ui.theme.Typography @Composable fun DetailListScreen( @@ -57,7 +52,8 @@ fun DetailListScreen( ) { val detailListViewState by detailListViewModel.viewState.collectAsState() - val pagingRestaurantList : LazyPagingItems = detailListViewModel.restaurantItems.collectAsLazyPagingItems() + val pagingRestaurantList: LazyPagingItems = + detailListViewModel.restaurantItems.collectAsLazyPagingItems() LaunchedEffect(Unit) { detailListViewModel.setEvent(DetailContract.DetailEvent.InitDetailScreen) @@ -77,59 +73,103 @@ fun DetailListScreen( EveryMealSortCategoryBottomSheetDialog( detailListViewState.detailSortCategoryType.title(), onClick = { - detailListViewModel.setEvent(DetailContract.DetailEvent.OnClickDetailListCategoryType(it.DetailSortCategoryType())) - detailListViewModel.setEvent(DetailContract.DetailEvent.SortBottomSheetStateChange(false)) + detailListViewModel.setEvent( + DetailContract.DetailEvent.OnClickDetailListCategoryType( + it.DetailSortCategoryType(), + ), + ) + detailListViewModel.setEvent( + DetailContract.DetailEvent.SortBottomSheetStateChange( + false, + ), + ) }, onDismiss = { - detailListViewModel.setEvent(DetailContract.DetailEvent.SortBottomSheetStateChange(false)) - } + detailListViewModel.setEvent( + DetailContract.DetailEvent.SortBottomSheetStateChange( + false, + ), + ) + }, ) } - if(detailListViewState.mealRatingBottomSheetState) { + if (detailListViewState.mealRatingBottomSheetState) { EveryMealCategoryRatingBottomSheetDialog( title, detailListViewState.rating, detailListViewState.restaurantCategoryType.title(), onClick = { - detailListViewModel.setEvent(DetailContract.DetailEvent.MealRatingBottomSheetStateChange(false)) + detailListViewModel.setEvent( + DetailContract.DetailEvent.MealRatingBottomSheetStateChange( + false, + ), + ) }, onDismiss = { - detailListViewModel.setEvent(DetailContract.DetailEvent.MealRatingBottomSheetStateChange(false)) + detailListViewModel.setEvent( + DetailContract.DetailEvent.MealRatingBottomSheetStateChange( + false, + ), + ) }, onCategoryClick = { - detailListViewModel.setEvent(DetailContract.DetailEvent.OnClickRestaurantCategoryType(it.RestaurantCategoryType())) + detailListViewModel.setEvent( + DetailContract.DetailEvent.OnClickRestaurantCategoryType( + it.RestaurantCategoryType(), + ), + ) }, onRatingClick = { detailListViewModel.setEvent(DetailContract.DetailEvent.OnClickRating(it)) - } + }, ) } - if(detailListViewState.reportBottomSheetState) { + if (detailListViewState.reportBottomSheetState) { EveryMealReportBottomSheetDialog( onClick = { - detailListViewModel.setEvent(DetailContract.DetailEvent.ReportBottomSheetStateChange(false)) - detailListViewModel.setEvent(DetailContract.DetailEvent.DetailReportBottomSheetStateChange(true)) + detailListViewModel.setEvent( + DetailContract.DetailEvent.ReportBottomSheetStateChange( + false, + ), + ) + detailListViewModel.setEvent( + DetailContract.DetailEvent.DetailReportBottomSheetStateChange( + true, + ), + ) }, onDismiss = { - detailListViewModel.setEvent(DetailContract.DetailEvent.ReportBottomSheetStateChange(false)) - } + detailListViewModel.setEvent( + DetailContract.DetailEvent.ReportBottomSheetStateChange( + false, + ), + ) + }, ) } - if(detailListViewState.detailReportBottomSheetState) { + if (detailListViewState.detailReportBottomSheetState) { EveryMealDetailReportBottomSheetDialog( detailListViewState.reportCategoryType.title(), onClick = { - detailListViewModel.setEvent(DetailContract.DetailEvent.DetailReportBottomSheetStateChange(false)) + detailListViewModel.setEvent( + DetailContract.DetailEvent.DetailReportBottomSheetStateChange( + false, + ), + ) }, onDismiss = { - detailListViewModel.setEvent(DetailContract.DetailEvent.DetailReportBottomSheetStateChange(false)) + detailListViewModel.setEvent( + DetailContract.DetailEvent.DetailReportBottomSheetStateChange( + false, + ), + ) }, onReportCategoryClick = { detailListViewModel.setEvent(DetailContract.DetailEvent.OnClickReportCategoryType(it.ReportCategoryType())) - } + }, ) } @@ -138,33 +178,41 @@ fun DetailListScreen( SaveTopBar(title = title) { navigateToPreviousScreen() } - } + }, ) { innerPadding -> LazyColumn( - modifier = Modifier.padding(innerPadding) + modifier = Modifier.padding(innerPadding), ) { item { Spacer(modifier = Modifier.padding(8.dp)) Row( modifier = Modifier .fillMaxSize() - .padding(horizontal = 20.dp) + .padding(horizontal = 20.dp), ) { DetailScreenChip( title = detailListViewState.detailSortCategoryType.title(), onChipClicked = { - detailListViewModel.setEvent(DetailContract.DetailEvent.SortBottomSheetStateChange(true)) - } + detailListViewModel.setEvent( + DetailContract.DetailEvent.SortBottomSheetStateChange( + true, + ), + ) + }, ) Spacer(modifier = Modifier.padding(4.dp)) DetailScreenChip( title = "필터", onChipClicked = { - detailListViewModel.setEvent(DetailContract.DetailEvent.MealRatingBottomSheetStateChange(true)) + detailListViewModel.setEvent( + DetailContract.DetailEvent.MealRatingBottomSheetStateChange( + true, + ), + ) }, - detailListViewState = detailListViewState + detailListViewState = detailListViewState, ) - if(detailListViewState.restaurantCategoryType.title().isNotEmpty()) { + if (detailListViewState.restaurantCategoryType.title().isNotEmpty()) { Spacer(modifier = Modifier.padding(4.dp)) DetailScreenChip( title = detailListViewState.restaurantCategoryType.title(), @@ -172,10 +220,10 @@ fun DetailListScreen( onChipClicked = { detailListViewModel.setEvent(DetailContract.DetailEvent.OnDeleteClickRestaurantCategoryType) }, - detailListViewState = detailListViewState + detailListViewState = detailListViewState, ) } - if(detailListViewState.rating != 0) { + if (detailListViewState.rating != 0) { Spacer(modifier = Modifier.padding(4.dp)) DetailScreenChip( title = "${detailListViewState.rating}", @@ -184,7 +232,7 @@ fun DetailListScreen( onChipClicked = { detailListViewModel.setEvent(DetailContract.DetailEvent.OnDeleteClickRating) }, - detailListViewState = detailListViewState + detailListViewState = detailListViewState, ) } // DetailScreenChip( @@ -220,7 +268,7 @@ fun DetailScreenChip( isCategory: Boolean = true, isRating: Boolean = false, onChipClicked: () -> Unit, - detailListViewState: DetailContract.DetailState? = null + detailListViewState: DetailContract.DetailState? = null, ) { val isRatingOrCategory = detailListViewState?.let { it.rating != 0 || it.restaurantCategoryType.name != "NONE" @@ -229,7 +277,7 @@ fun DetailScreenChip( Surface( modifier = Modifier.clickable( indication = null, - interactionSource = remember { MutableInteractionSource() } + interactionSource = remember { MutableInteractionSource() }, ) { onChipClicked() }, color = when { detailListViewState == null -> Grey2 @@ -239,16 +287,16 @@ fun DetailScreenChip( shape = RoundedCornerShape(100.dp), ) { Row( - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { - if(isRating) { + if (isRating) { Image( modifier = Modifier .padding(start = 12.dp, top = 8.dp, bottom = 8.dp) .size(16.dp), imageVector = ImageVector.vectorResource(id = R.drawable.icon_gray_star_mono), contentDescription = "gray_star", - colorFilter = ColorFilter.tint(Main100) + colorFilter = ColorFilter.tint(Main100), ) } Text( @@ -259,10 +307,15 @@ fun DetailScreenChip( else -> Grey7 }, fontSize = 14.sp, - modifier = Modifier.padding(start = if(!isRating) 12.dp else 4.dp, end = 4.dp, top = 6.dp, bottom = 6.dp), - fontWeight = FontWeight.SemiBold + modifier = Modifier.padding( + start = if (!isRating) 12.dp else 4.dp, + end = 4.dp, + top = 6.dp, + bottom = 6.dp, + ), + fontWeight = FontWeight.SemiBold, ) - if(isCategory) { + if (isCategory) { Image( modifier = Modifier .padding(end = 12.dp) @@ -273,7 +326,7 @@ fun DetailScreenChip( detailListViewState == null -> ColorFilter.tint(Grey7) isRatingOrCategory -> ColorFilter.tint(Main100) else -> ColorFilter.tint(Grey7) - } + }, ) } else { Image( @@ -281,7 +334,7 @@ fun DetailScreenChip( .padding(end = 12.dp) .size(12.dp), imageVector = ImageVector.vectorResource(id = R.drawable.icon_x_mono_12), - contentDescription = "close" + contentDescription = "close", ) } } @@ -308,7 +361,6 @@ fun PreviewDetailScreenChip() { title = "최신순", isCategory = true, onChipClicked = { - - } + }, ) -} \ No newline at end of file +} 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 f07ac298..8bf0e46f 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 @@ -10,7 +10,6 @@ import com.everymeal.presentation.base.BaseViewModel import com.everymeal.presentation.ui.detail.DetailContract.DetailEvent import com.everymeal.presentation.ui.detail.DetailContract.DetailState import com.everymeal.presentation.ui.detail.DetailContract.DetailEffect -import com.everymeal.presentation.ui.signup.UnivSelectContract import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -131,4 +130,4 @@ class DetailListViewModel @Inject constructor( ) } } -} \ No newline at end of file +} 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 9e9e75c2..43a90fa0 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,5 @@ package com.everymeal.presentation.ui.home -import com.everymeal.domain.model.restaurant.GetUnivRestaurantEntity import com.everymeal.domain.model.restaurant.RestaurantDataEntity import com.everymeal.presentation.base.LoadState import com.everymeal.presentation.base.ViewEvent @@ -32,7 +31,7 @@ enum class DetailListScreenType { RECOMMEND, RESTAURANT, CAFE, - DRINK + DRINK, } fun String.DetailListScreenType(): DetailListScreenType { @@ -52,4 +51,4 @@ fun DetailListScreenType.title(): String { DetailListScreenType.CAFE -> "카페" DetailListScreenType.DRINK -> "술집" } -} \ No newline at end of file +} 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 d42b6daf..c54a6f5f 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 @@ -39,7 +39,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel -import com.everymeal.domain.model.restaurant.RestaurantDataEntity import com.everymeal.presentation.R import com.everymeal.presentation.base.LoadState import com.everymeal.presentation.components.EveryMealLineButton @@ -57,9 +56,10 @@ import com.everymeal.presentation.ui.theme.Paddings @Composable fun HomeScreen( - homeViewModel : HomeViewModel = hiltViewModel(), - onDetailScreenClickType : (String) -> Unit, - onDetailRestaurantClick : (String) -> Unit, + homeViewModel: HomeViewModel = hiltViewModel(), + onDetailScreenClickType: (String) -> Unit, + onDetailRestaurantClick: (String) -> Unit, + onReviewBottomSheetClick: () -> Unit, ) { LaunchedEffect(Unit) { @@ -72,6 +72,7 @@ fun HomeScreen( is HomeContract.HomeEffect.NavigateToDetailListScreen -> { onDetailScreenClickType(effect.detailListScreenType.title()) } + is HomeContract.HomeEffect.NavigateToDetailRestaurant -> { onDetailRestaurantClick(effect.restaurantId.toString()) } @@ -115,7 +116,8 @@ fun HomeScreen( title = stringResource(id = R.string.univ_admin_review_title), content = stringResource(id = R.string.univ_admin_review_content), onClick = { - + onReviewBottomSheetClick() + homeViewModel.setEvent(HomeContract.HomeEvent.BottomSheetStateChange(false)) }, onDismiss = { homeViewModel.setEvent(HomeContract.HomeEvent.BottomSheetStateChange(false)) @@ -123,10 +125,11 @@ fun HomeScreen( ) } - when(homeViewState.uiState) { + when (homeViewState.uiState) { LoadState.LOADING -> { EveryMealLoadingDialog() } + LoadState.SUCCESS -> { Column( modifier = Modifier @@ -140,7 +143,11 @@ fun HomeScreen( ) { item { HomeMainTopLayout { - homeViewModel.setEvent(HomeContract.HomeEvent.BottomSheetStateChange(true)) + homeViewModel.setEvent( + HomeContract.HomeEvent.BottomSheetStateChange( + true + ) + ) } HomeCategoryList { homeViewModel.setEvent(HomeContract.HomeEvent.OnClickDetailList(it.DetailListScreenType())) @@ -161,7 +168,11 @@ fun HomeScreen( }, onDetailClick = { - homeViewModel.setEvent(HomeContract.HomeEvent.OnClickDetailRestaurant(it)) + homeViewModel.setEvent( + HomeContract.HomeEvent.OnClickDetailRestaurant( + it + ) + ) } ) Spacer(modifier = Modifier.padding(10.dp)) @@ -217,6 +228,7 @@ fun HomeScreen( } } } + LoadState.ERROR -> { } @@ -237,7 +249,6 @@ fun HomeTopAppBar() { actions = { IconButton( onClick = { - }, ) { Icon( @@ -247,7 +258,6 @@ fun HomeTopAppBar() { } IconButton( onClick = { - }, ) { Icon( @@ -273,7 +283,7 @@ fun HomeMainTopLayout( .background(Gray300, RoundedCornerShape(12.dp)) .clickable( indication = null, - interactionSource = remember { MutableInteractionSource() } + interactionSource = remember { MutableInteractionSource() }, ) { onClick() } @@ -291,29 +301,29 @@ fun HomeMainTopLayout( text = stringResource(id = R.string.home_top_category_title, "슈니"), fontSize = 15.sp, style = EveryMealTypo.displaySmall, - color = Gray800 + color = Gray800, ) Text( text = stringResource(R.string.home_top_category_sub_title), style = EveryMealTypo.labelSmall, fontSize = 14.sp, - color = Gray500 + color = Gray500, ) } Spacer(modifier = Modifier.weight(1f)) Icon( imageVector = ImageVector.vectorResource(R.drawable.icon_arrow_right), contentDescription = stringResource(R.string.icon_arrow_right), - tint = Gray500 + tint = Gray500, ) } } @Composable fun HomeCategoryList( - isBottomSheet : Boolean = false, + isBottomSheet: Boolean = false, restaurantCategoryType: String = "", - onClick: (String) -> Unit + onClick: (String) -> Unit, ) { val horizotalDp = if (isBottomSheet) 0.dp else 20.dp @@ -321,13 +331,13 @@ fun HomeCategoryList( modifier = Modifier .fillMaxWidth() .padding(horizontal = horizotalDp), - horizontalArrangement = Arrangement.SpaceBetween + horizontalArrangement = Arrangement.SpaceBetween, ) { CategoryItem( isBottomSheet, restaurantCategoryType, R.drawable.ic_homemenu_recommend, - R.string.home_top_category_recommend + R.string.home_top_category_recommend, ) { onClick("추천") } @@ -335,7 +345,7 @@ fun HomeCategoryList( isBottomSheet, restaurantCategoryType, R.drawable.ic_homemenu_bap, - R.string.home_top_category_rice + R.string.home_top_category_rice, ) { onClick("밥집") } @@ -343,7 +353,7 @@ fun HomeCategoryList( isBottomSheet, restaurantCategoryType, R.drawable.ic_homemenu_cake, - R.string.home_top_category_cafe + R.string.home_top_category_cafe, ) { onClick("카페") } @@ -351,7 +361,7 @@ fun HomeCategoryList( isBottomSheet, restaurantCategoryType, R.drawable.ic_homemenu_beer, - R.string.home_top_category_drink + R.string.home_top_category_drink, ) { onClick("술집") } @@ -364,7 +374,7 @@ fun HomeDivider() { modifier = Modifier .fillMaxWidth() .background(color = Gray100) - .height(12.dp) + .height(12.dp), ) } @@ -399,7 +409,7 @@ fun CategoryItem( restaurantCategoryType == stringResource(categoryText) -> Gray100 else -> Color.White }, - RoundedCornerShape(12.dp) + RoundedCornerShape(12.dp), ) .padding(horizontal = 17.dp, vertical = 4.dp), horizontalAlignment = Alignment.CenterHorizontally, @@ -411,7 +421,7 @@ fun CategoryItem( Text( text = stringResource(categoryText), fontSize = 12.sp, - color = Color.Black + color = Color.Black, ) } } @@ -424,6 +434,7 @@ fun HomeScreenPreview() { HomeScreen( onDetailScreenClickType = {}, onDetailRestaurantClick = {}, + onReviewBottomSheetClick = {}, ) } } 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 0c861883..a0117795 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 @@ -7,8 +7,8 @@ import com.everymeal.domain.usecase.restaurant.GetUnivRestaurantUseCase import com.everymeal.presentation.base.BaseViewModel import com.everymeal.presentation.base.LoadState import com.everymeal.presentation.ui.home.HomeContract.HomeEffect -import com.everymeal.presentation.ui.home.HomeContract.HomeState import com.everymeal.presentation.ui.home.HomeContract.HomeEvent +import com.everymeal.presentation.ui.home.HomeContract.HomeState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -22,7 +22,7 @@ data class Review( val rating: Int, val reviewDate: String, val content: String, - val restaurantName: String + val restaurantName: String, ) data class Restaurant( @@ -50,10 +50,11 @@ class HomeViewModel @Inject constructor( is HomeEvent.OnClickDetailList -> { sendEffect({ HomeEffect.NavigateToDetailListScreen(event.detailListScreenType) }) } + is HomeEvent.BottomSheetStateChange -> { updateState { copy( - bottomSheetState = event.bottomSheetState + bottomSheetState = event.bottomSheetState, ) } } @@ -86,4 +87,4 @@ class HomeViewModel @Inject constructor( } } } -} \ No newline at end of file +} 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 75f88b56..32464c8d 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 @@ -22,6 +22,8 @@ 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.restaurant.DetailRestaurantScreen +import com.everymeal.presentation.ui.review.search.ReviewSearchScreen +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 @@ -47,10 +49,10 @@ fun MainScreen( navController = navController, navigationItem = navigationItem, ) - } + }, ) } - } + }, ) { padding -> NavHost( modifier = Modifier.padding(padding), @@ -64,7 +66,10 @@ fun MainScreen( }, onDetailRestaurantClick = { detailRestaurantIdx -> navController.navigate(EveryMealRoute.DETAIL_RESTAURANT.route.plus("/$detailRestaurantIdx")) - } + }, + onReviewBottomSheetClick = { + navController.navigate(EveryMealRoute.REVIEW_SEARCH.route) + }, ) } composable(route = EveryMealRoute.UNIV_FOOD.route) { @@ -102,12 +107,18 @@ fun MainScreen( SchoolAuthScreen( onSuccessEmailVerification = { navController.popBackStack() - } + }, ) } + composable(route = EveryMealRoute.REVIEW_SEARCH.route) { + ReviewSearchScreen(navController = navController) + } composable(route = EveryMealRoute.WITH_DRAW.route) { WithDrawScreen() } + composable(route = EveryMealRoute.SEARCH.route) { + SearchScreen(navController = navController) + } } } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/restaurant/DetailRestaurantContract.kt b/presentation/src/main/java/com/everymeal/presentation/ui/restaurant/DetailRestaurantContract.kt index 5b13177f..4f77cc45 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/restaurant/DetailRestaurantContract.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/restaurant/DetailRestaurantContract.kt @@ -5,7 +5,6 @@ import com.everymeal.presentation.base.LoadState import com.everymeal.presentation.base.ViewEvent import com.everymeal.presentation.base.ViewSideEffect import com.everymeal.presentation.base.ViewState -import com.everymeal.presentation.ui.signup.UnivSelectContract data class DetailRestaurantState( @@ -38,4 +37,4 @@ sealed class DetailRestaurantEvent : ViewEvent { sealed class DetailRestaurantEffect : ViewSideEffect { -} \ 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 f5490e38..7b7296bb 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 @@ -41,7 +41,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale -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 @@ -49,15 +48,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel -import coil.compose.AsyncImage -import coil.compose.rememberImagePainter import com.everymeal.domain.model.restaurant.RestaurantDataEntity +import coil.compose.AsyncImage import com.everymeal.presentation.R import com.everymeal.presentation.base.LoadState import com.everymeal.presentation.components.EveryMealDialog import com.everymeal.presentation.components.EveryMealLoadingDialog import com.everymeal.presentation.ui.save.SaveTopBar -import com.everymeal.presentation.ui.signup.UnivSelectContract import com.everymeal.presentation.ui.theme.EveryMealTypo import com.everymeal.presentation.ui.theme.Gray100 import com.everymeal.presentation.ui.theme.Gray300 @@ -82,13 +79,18 @@ fun DetailRestaurantScreen( val restaurantInfo = viewState.restaurantInfo LaunchedEffect(Unit) { - detailRestaurantViewModel.setEvent(DetailRestaurantEvent.InitDetailRestaurantScreen(restaurantId)) + detailRestaurantViewModel.setEvent( + DetailRestaurantEvent.InitDetailRestaurantScreen( + restaurantId + ) + ) } - when(viewState.getDetailRestaurantState) { + when (viewState.getDetailRestaurantState) { LoadState.LOADING -> { EveryMealLoadingDialog() } + LoadState.SUCCESS -> { Scaffold( topBar = { @@ -114,12 +116,12 @@ fun DetailRestaurantScreen( ) { Image( imageVector = - if(viewState.isFabClicked) + if (viewState.isFabClicked) ImageVector.vectorResource(R.drawable.icon_x_mono) else ImageVector.vectorResource(R.drawable.icon_pencil_mono), contentDescription = "floating", - colorFilter = ColorFilter.tint(if(viewState.isFabClicked) Gray800 else Color.White), + colorFilter = ColorFilter.tint(if (viewState.isFabClicked) Gray800 else Color.White), ) } }, @@ -129,7 +131,7 @@ fun DetailRestaurantScreen( modifier = Modifier.padding(innerPadding), ) { // Todo Test 필요 - if(!restaurantInfo.images.isNullOrEmpty()) { + if (!restaurantInfo.images.isNullOrEmpty()) { item { DetailRestaurantImage( restaurantInfo = restaurantInfo @@ -160,7 +162,7 @@ fun DetailRestaurantScreen( ) ) }, - contentAlignment=Alignment.BottomEnd + contentAlignment = Alignment.BottomEnd ) { Row( modifier = Modifier @@ -204,8 +206,9 @@ fun DetailRestaurantScreen( } } } + LoadState.ERROR -> { - if(viewState.networkErrorDialog) { + if (viewState.networkErrorDialog) { EveryMealDialog( modifier = Modifier.fillMaxWidth(), title = stringResource(R.string.error_dialog_title), @@ -214,12 +217,28 @@ fun DetailRestaurantScreen( dismissButtonText = stringResource(R.string.cancel), onDismissRequest = { }, onConfirmClick = { - detailRestaurantViewModel.setEvent(DetailRestaurantEvent.NetworkErrorDialogClicked(false)) - detailRestaurantViewModel.setEvent(DetailRestaurantEvent.InitDetailRestaurantScreen(restaurantId)) - detailRestaurantViewModel.setEvent(DetailRestaurantEvent.NetworkErrorDialogClicked(true)) + detailRestaurantViewModel.setEvent( + DetailRestaurantEvent.NetworkErrorDialogClicked( + false + ) + ) + detailRestaurantViewModel.setEvent( + DetailRestaurantEvent.InitDetailRestaurantScreen( + restaurantId + ) + ) + detailRestaurantViewModel.setEvent( + DetailRestaurantEvent.NetworkErrorDialogClicked( + true + ) + ) }, onDisMissClicked = { - detailRestaurantViewModel.setEvent(DetailRestaurantEvent.NetworkErrorDialogClicked(false)) + detailRestaurantViewModel.setEvent( + DetailRestaurantEvent.NetworkErrorDialogClicked( + false + ) + ) onNetWorkErrorCancelClick() } ) @@ -275,7 +294,7 @@ fun DetailRestaurantImage( @Composable fun DetailRestaurantMainInfo( - restaurantInfo : RestaurantDataEntity + restaurantInfo: RestaurantDataEntity ) { Column( modifier = Modifier @@ -388,8 +407,8 @@ fun DetailRestaurantMainInfo( @OptIn(ExperimentalFoundationApi::class) @Composable fun DetailRestaurantTabLayout( - restaurantInfo : RestaurantDataEntity, - viewModel : DetailRestaurantViewModel + restaurantInfo: RestaurantDataEntity, + viewModel: DetailRestaurantViewModel ) { val viewState by viewModel.viewState.collectAsState() @@ -420,10 +439,12 @@ fun DetailRestaurantTabLayout( ) { pages.forEachIndexed { index, title -> Tab( - text = { Text( - text = title, - style = EveryMealTypo.labelSmall, - ) }, + text = { + Text( + text = title, + style = EveryMealTypo.labelSmall, + ) + }, selected = viewState.selectedTabIndex == index, onClick = { coroutineScope.launch { @@ -445,6 +466,7 @@ fun DetailRestaurantTabLayout( 1 -> DetailRestaurantTabImage( restaurantInfo = restaurantInfo ) + 2 -> DetailRestaurantReview() } } @@ -452,7 +474,7 @@ fun DetailRestaurantTabLayout( @Composable fun DetailRestaurantTabInfo( - restaurantInfo : RestaurantDataEntity, + restaurantInfo: RestaurantDataEntity, modifier: Modifier = Modifier, ) { Column( diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/restaurant/DetailRestaurantViewModel.kt b/presentation/src/main/java/com/everymeal/presentation/ui/restaurant/DetailRestaurantViewModel.kt index b00753c2..ff3443f0 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/restaurant/DetailRestaurantViewModel.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/restaurant/DetailRestaurantViewModel.kt @@ -6,7 +6,6 @@ import com.everymeal.domain.usecase.restaurant.GetDetailRestaurantUseCase import com.everymeal.presentation.base.BaseViewModel import com.everymeal.presentation.base.LoadState import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import javax.inject.Inject @@ -71,4 +70,4 @@ class DetailRestaurantViewModel @Inject constructor( ) } } -} \ No newline at end of file +} 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 374c66b1..8e5dcee4 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 @@ -43,19 +43,17 @@ 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.search.topbar.SearchBar 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 - @Composable fun ReviewScreen( - viewModel: ReviewScreenViewModel = hiltViewModel() + viewModel: ReviewScreenViewModel = hiltViewModel(), ) { val pickMultipleMedia = rememberLauncherForActivityResult( - ActivityResultContracts.PickMultipleVisualMedia(10) + ActivityResultContracts.PickMultipleVisualMedia(10), ) { viewModel.setEvent(ReviewEvent.OnImageSelected(it)) Log.d("ReviewScreen", "ReviewScreen: $it") @@ -66,12 +64,10 @@ fun ReviewScreen( topBar = { ReviewTopBar( title = stringResource(R.string.review_write), - onBackClicked = { - - }, + onBackClicked = {}, ) }, - containerColor = Color.White + containerColor = Color.White, ) { innerPadding -> Box(modifier = Modifier.padding(innerPadding)) { Column { @@ -99,19 +95,29 @@ fun ReviewScreen( 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 + Toast.LENGTH_SHORT, ).show() }, onAddPhotoClicked = { pickMultipleMedia.launch( PickVisualMediaRequest( - ActivityResultContracts.PickVisualMedia.ImageOnly - ) + ActivityResultContracts.PickVisualMedia.ImageOnly, + ), ) - } + }, ) } } @@ -127,7 +133,7 @@ fun ColumnScope.StarDetail( Column( modifier = modifier .align(Alignment.CenterHorizontally) - .padding(top = 133.dp) + .padding(top = 133.dp), ) { RestaurantType( viewState = viewState, @@ -139,7 +145,7 @@ fun ColumnScope.StarDetail( modifier = Modifier .padding(top = 50.dp), ratingStateList = viewState.starRatingStateList, - starRatingClicked = startRatingClicked + starRatingClicked = startRatingClicked, ) } } @@ -154,7 +160,7 @@ fun ColumnScope.RestaurantType( .align(Alignment.CenterHorizontally) .background( color = Grey2, - shape = RoundedCornerShape(4.dp) + shape = RoundedCornerShape(4.dp), ) .padding(horizontal = 6.dp, vertical = 3.dp), text = viewState.restaurantType, @@ -170,7 +176,7 @@ fun ColumnScope.RestaurantType( @Composable fun RestaurantName( modifier: Modifier = Modifier, - viewState: ReviewState + viewState: ReviewState, ) { Text( modifier = modifier, @@ -184,21 +190,6 @@ fun RestaurantName( ) } -@Composable -fun ReviewSearchBar( - modifier: Modifier = Modifier, - searchBarClicked: () -> Unit -) { - SearchBar( - modifier = modifier.clickable { - searchBarClicked() - }, - searchQuery = "", - changeQuery = {}, - setShowHistory = {} - ) -} - @Composable fun StarRating( modifier: Modifier = Modifier, @@ -208,7 +199,7 @@ fun StarRating( ) { LazyRow( modifier = modifier, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { itemsIndexed(ratingStateList) { index, active -> Image( @@ -220,44 +211,32 @@ fun StarRating( }, painter = if (active.value) { painterResource( - id = R.drawable.icon_active_star_mono + id = R.drawable.icon_active_star_mono, ) } else { painterResource( - id = R.drawable.icon_unactive_star_mono + id = R.drawable.icon_unactive_star_mono, ) }, - contentDescription = null + contentDescription = null, ) } } } -@Composable -fun ReviewGuideHeader( - modifier: Modifier = Modifier -) { - Text( - modifier = modifier - .padding(start = 24.dp, top = 48.dp), - text = stringResource(R.string.review_guide_header), - style = Typography.titleLarge, - ) -} - @OptIn(ExperimentalMaterial3Api::class) @Composable fun ReviewTopBar( modifier: Modifier = Modifier, title: String, - onBackClicked: () -> Unit + onBackClicked: () -> Unit, ) { TopAppBar( title = { Text( text = title, style = Typography.bodySmall, - color = Grey9 + color = Grey9, ) }, actions = { @@ -268,15 +247,15 @@ fun ReviewTopBar( .padding(end = 4.dp) .clickable(onClick = onBackClicked), painter = painterResource(id = R.drawable.icon_x_mono), - contentDescription = null + contentDescription = null, ) - } + }, ) } @Composable fun ReviewSaveDialog( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val showDialog = remember { mutableStateOf(true) } if (showDialog.value) { @@ -292,7 +271,7 @@ fun ReviewSaveDialog( }, onDisMissClicked = { showDialog.value = false - } + }, ) } } 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 index 36974eee..4f9d8ea0 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreenContract.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreenContract.kt @@ -8,34 +8,41 @@ import com.everymeal.presentation.base.ViewSideEffect import com.everymeal.presentation.base.ViewState data class ReviewState( - var starRatingStateList: List> = listOf( + val starRatingStateList: List> = listOf( mutableStateOf(false), mutableStateOf(false), mutableStateOf(false), mutableStateOf(false), mutableStateOf(false), ), - var imageUri: List = listOf(), - var restaurantType: String = "주점", - var restaurantName: String = "성신 이자카야", - var reviewValue: String = "" + 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 + val starIndex: Int, ) : ReviewEvent() data class OnReviewTextChanged( - val reviewValue: String + val reviewValue: String, ) : ReviewEvent() data class OnImageSelected( - val imageUri: List + val imageUri: List, ) : ReviewEvent() -} - -sealed class ReviewEffect : ViewSideEffect { + 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 index bd837bc1..84ba725e 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreenViewModel.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/review/ReviewScreenViewModel.kt @@ -1,23 +1,27 @@ 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) { + when (event) { is ReviewEvent.OnStarClicked -> { updateState { val newStarRatingStateList = List(starRatingStateList.size) { index -> mutableStateOf(index <= event.starIndex) } copy( - starRatingStateList = newStarRatingStateList + starRatingStateList = newStarRatingStateList, ) } } @@ -25,7 +29,7 @@ class ReviewScreenViewModel @Inject constructor( is ReviewEvent.OnReviewTextChanged -> { updateState { copy( - reviewValue = event.reviewValue + reviewValue = event.reviewValue, ) } } @@ -33,8 +37,20 @@ class ReviewScreenViewModel @Inject constructor( is ReviewEvent.OnImageSelected -> { updateState { copy( - imageUri = event.imageUri + 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/detail/ReviewDetailScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/review/detail/ReviewDetailScreen.kt index 817c1fab..7eeeb274 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/review/detail/ReviewDetailScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/review/detail/ReviewDetailScreen.kt @@ -1,9 +1,6 @@ package com.everymeal.presentation.ui.review.detail import androidx.compose.foundation.Image -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.rememberScrollableState -import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -45,7 +42,7 @@ fun ReviewDetailScreen() { mutableStateOf(true), mutableStateOf(true), mutableStateOf(true), - mutableStateOf(true) + mutableStateOf(true), ) } Scaffold( @@ -53,26 +50,25 @@ fun ReviewDetailScreen() { ReviewTopBar( title = stringResource(R.string.review_title), onBackClicked = { - }, ) - } + }, ) { innerPadding -> val scrollState = rememberScrollState() Column( modifier = Modifier .padding(innerPadding) - .verticalScroll(scrollState) + .verticalScroll(scrollState), ) { UserProfileAppbar( userName = "햄식이", - ratingList = mockRatingList + ratingList = mockRatingList, ) FoodImage( modifier = Modifier .fillMaxWidth() .aspectRatio(1f) - .padding(top = 24.dp) + .padding(top = 24.dp), ) ReviewText( modifier = Modifier.padding( @@ -88,13 +84,13 @@ fun ReviewDetailScreen() { private fun UserProfileAppbar( userName: String, userProfileUrl: String? = null, - ratingList: List> + ratingList: List>, ) { Row { Image( modifier = Modifier.size(40.dp), painter = painterResource(id = R.drawable.profile_ex_image), - contentDescription = "profile" + contentDescription = "profile", ) Column(modifier = Modifier.padding(start = 12.dp)) { Text( @@ -104,12 +100,12 @@ private fun UserProfileAppbar( color = Gray800, ) Row( - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { StarRating( modifier = Modifier.padding(horizontal = 1.dp), ratingStateList = ratingList, - starSize = 14.dp + starSize = 14.dp, ) // TODO 시간 계산 필요 Text( @@ -127,64 +123,64 @@ private fun UserProfileAppbar( .size(24.dp) .padding(end = 20.dp), painter = painterResource(id = R.drawable.icon_dots_mono), - contentDescription = "more" + contentDescription = "more", ) } } @Composable private fun FoodImage( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Box( - modifier = modifier + modifier = modifier, ) { Image( modifier = Modifier.fillMaxSize(), painter = painterResource(id = R.drawable.food_ex_2), - contentDescription = "more" + contentDescription = "more", ) LocationButton( modifier = Modifier .align(Alignment.BottomStart) .padding( start = 16.dp, - bottom = 16.dp - ) + bottom = 16.dp, + ), ) PageInfo( modifier = Modifier .align(Alignment.BottomEnd) .padding( end = 16.dp, - bottom = 16.dp - ) + bottom = 16.dp, + ), ) } } @Composable private fun LocationButton( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Surface( modifier = modifier, color = Color(0x99000000), - shape = RoundedCornerShape(size = 6.dp) + shape = RoundedCornerShape(size = 6.dp), ) { Row( modifier = Modifier.padding( horizontal = 6.dp, - vertical = 4.dp + vertical = 4.dp, ), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { Image( modifier = Modifier.size(16.dp), painter = painterResource( - id = R.drawable.icon_pin_location_mono + id = R.drawable.icon_pin_location_mono, ), - contentDescription = "location" + contentDescription = "location", ) Spacer(modifier = Modifier.padding(4.dp)) Text( @@ -196,7 +192,7 @@ private fun LocationButton( Image( modifier = Modifier.size(16.dp), painter = painterResource(id = R.drawable.icon_arrow_right), - contentDescription = null + contentDescription = null, ) } } @@ -204,17 +200,17 @@ private fun LocationButton( @Composable private fun PageInfo( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Surface( modifier = modifier, color = Color(0x99000000), - shape = RoundedCornerShape(size = 20.dp) + shape = RoundedCornerShape(size = 20.dp), ) { Text( modifier = Modifier.padding( horizontal = 8.dp, - vertical = 2.dp + vertical = 2.dp, ), text = "1/3", fontSize = 14.sp, @@ -226,7 +222,7 @@ private fun PageInfo( @Composable fun ReviewText( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Text( modifier = modifier, diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/review/search/ReviewSearchScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/review/search/ReviewSearchScreen.kt new file mode 100644 index 00000000..29065486 --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/ui/review/search/ReviewSearchScreen.kt @@ -0,0 +1,65 @@ +package com.everymeal.presentation.ui.review.search + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.everymeal.presentation.R +import com.everymeal.presentation.ui.bottom.EveryMealRoute +import com.everymeal.presentation.ui.review.ReviewTopBar +import com.everymeal.presentation.ui.theme.Typography + +@Composable +fun ReviewSearchScreen( + navController: NavController, +) { + Scaffold( + topBar = { + ReviewTopBar( + title = stringResource(R.string.review_write), + onBackClicked = { + navController.popBackStack() + }, + ) + }, + containerColor = Color.White, + ) { innerPadding -> + Box(modifier = Modifier.padding(innerPadding)) { + Column { + ReviewGuideHeader() + Image( + modifier = Modifier + .padding(top = 28.dp) + .padding(horizontal = 20.dp) + .clickable { + navController.navigate(EveryMealRoute.SEARCH.route) + }, + painter = painterResource(id = R.drawable.img_review_searchbar), + contentDescription = null + ) + } + } + } +} + +@Composable +fun ReviewGuideHeader( + modifier: Modifier = Modifier, +) { + Text( + modifier = modifier + .padding(start = 24.dp, top = 48.dp), + text = stringResource(R.string.review_guide_header), + style = Typography.titleLarge, + ) +} 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 6712f348..8cc9fad9 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 @@ -49,29 +49,29 @@ fun ReviewWriteScreen( starRatingClicked: (Int) -> Unit, reviewTextChanged: (String) -> Unit, onReviewRegisterClicked: () -> Unit, - onAddPhotoClicked: () -> Unit + onAddPhotoClicked: () -> Unit, ) { Column( modifier = modifier .fillMaxWidth() .background(color = Color.White) .padding(horizontal = 20.dp), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { RestaurantType( - viewState = viewState + viewState = viewState, ) RestaurantName( modifier = Modifier .padding(top = 12.dp), - viewState = viewState + viewState = viewState, ) StarRating( modifier = modifier.padding(top = 16.dp), ratingStateList = viewState.starRatingStateList, starRatingClicked = starRatingClicked, - starSize = 20.dp + starSize = 20.dp, ) Spacer(modifier = Modifier.height(60.dp)) EveryMealTextField( @@ -85,11 +85,11 @@ fun ReviewWriteScreen( Spacer(modifier = Modifier.height(16.dp)) Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Start + horizontalArrangement = Arrangement.Start, ) { AddReviewPhoto( viewState = viewState, - addPhotoClicked = onAddPhotoClicked + addPhotoClicked = onAddPhotoClicked, ) LazyRow { items(viewState.imageUri) { @@ -100,7 +100,7 @@ fun ReviewWriteScreen( .clip(RoundedCornerShape(10.dp)), model = it, contentScale = ContentScale.Crop, - contentDescription = null + contentDescription = null, ) } } @@ -110,7 +110,7 @@ fun ReviewWriteScreen( modifier = Modifier .fillMaxWidth() .height(54.dp), - onReviewRegisterClicked = onReviewRegisterClicked + onReviewRegisterClicked = onReviewRegisterClicked, ) Spacer(modifier = Modifier.height(20.dp)) } @@ -128,20 +128,20 @@ private fun AddReviewPhoto( .clickable { addPhotoClicked() }, border = BorderStroke( width = 1.dp, - color = Gray200 + color = Gray200, ), shape = RoundedCornerShape(10.dp), - color = Color.White + color = Color.White, ) { Column( modifier = Modifier, horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + verticalArrangement = Arrangement.Center, ) { Image( modifier = Modifier.size(24.dp), painter = painterResource(id = R.drawable.icon_picture_mono), - contentDescription = null + contentDescription = null, ) Text( modifier = Modifier.padding(top = 2.dp), @@ -160,7 +160,7 @@ private fun AddReviewPhoto( @Composable private fun ReviewRegisterButton( modifier: Modifier = Modifier, - onReviewRegisterClicked: () -> Unit + onReviewRegisterClicked: () -> Unit, ) { Button( modifier = modifier, @@ -181,8 +181,8 @@ private fun ReviewRegisterButton( fontSize = 16.sp, fontWeight = FontWeight(500), color = Color.White, - ) + ), ) - } + }, ) } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/search/SearchContract.kt b/presentation/src/main/java/com/everymeal/presentation/ui/search/SearchContract.kt index c8c79b37..f217cacc 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/search/SearchContract.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/search/SearchContract.kt @@ -1,10 +1,10 @@ package com.everymeal.presentation.ui.search +import com.everymeal.domain.model.restaurant.RestaurantDataEntity import com.everymeal.presentation.base.ViewEvent import com.everymeal.presentation.base.ViewSideEffect import com.everymeal.presentation.base.ViewState - /* 대학교 불러오기 LoadState 대학교 선택하기 State Hoisting @@ -13,24 +13,19 @@ data class SearchState( val searchQuery: String = "", val searchIsShowHistory: Boolean = true, val searchHistoryItems: List = listOf(), + val searchResultList: List = listOf(), ) : ViewState - sealed class SearchEvent : ViewEvent { - data class SetShowSearchHistory( - val show: Boolean - ) : SearchEvent() - data class SearchQueryChanged( - val query: String + val query: String, ) : SearchEvent() data class UpdateSearchHistory( - val historyItems: List + val historyItems: List, ) : SearchEvent() + object SearchRestaurant : SearchEvent() } -sealed class SearchEffect : ViewSideEffect { - -} +sealed class SearchEffect : ViewSideEffect 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 94aeaba7..1474f54e 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 @@ -9,6 +9,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -17,6 +19,7 @@ import androidx.compose.runtime.collectAsState 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.TextStyle @@ -25,30 +28,35 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.everymeal.domain.model.restaurant.RestaurantDataEntity import com.everymeal.presentation.R +import com.everymeal.presentation.components.EveryMealRestaurantItem import com.everymeal.presentation.ui.search.history.SearchHistoryList -import com.everymeal.presentation.ui.search.topbar.TopBar +import com.everymeal.presentation.ui.search.topbar.SearchTopBar import com.everymeal.presentation.ui.theme.Gray800 @Composable fun SearchScreen( - viewModel: SearchViewModel = hiltViewModel() + navController: NavController, + viewModel: SearchViewModel = hiltViewModel(), ) { val viewState = viewModel.viewState.collectAsState() + Scaffold( topBar = { - TopBar( + SearchTopBar( + modifier = Modifier + .fillMaxWidth() + .padding(top = 14.dp, end = 20.dp), onBackClick = { }, - setShowHistory = { viewModel.setEvent(SearchEvent.SetShowSearchHistory(it)) }, searchQuery = viewState.value.searchQuery, changeQuery = { viewModel.setEvent(SearchEvent.SearchQueryChanged(it)) }, + onSearchAction = { viewModel.setEvent(SearchEvent.SearchRestaurant) }, ) }, - containerColor = Color.White + containerColor = Color.White, ) { innerPadding -> - SearchDetail( - modifier = Modifier.padding(innerPadding) - ) if (viewState.value.searchIsShowHistory) { SearchHistoryList( historyItems = viewState.value.searchHistoryItems, @@ -62,36 +70,48 @@ fun SearchScreen( modifier = Modifier .padding(innerPadding) .fillMaxWidth() - .padding(horizontal = 20.dp) + .padding(horizontal = 20.dp), ) } else { - EmptyView() + SearchDetail( + modifier = Modifier.padding(innerPadding), + searchResultList = viewState.value.searchResultList, + ) } } } @Composable fun SearchDetail( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + searchResultList: List, ) { - + LazyColumn(modifier = modifier) { + itemsIndexed(searchResultList) { index, restaurant -> + EveryMealRestaurantItem( + restaurant = restaurant, + onLoveClick = { }, + onDetailClick = { }, + ) + } + } } @Composable fun EmptyView( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Box( modifier = modifier.fillMaxSize(), - contentAlignment = Alignment.Center + contentAlignment = Alignment.Center, ) { Column( - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { Image( painter = painterResource(id = R.drawable.icon_store), contentDescription = null, - modifier = Modifier.size(40.dp) + modifier = Modifier.size(40.dp), ) Spacer(modifier = Modifier.heightIn(8.dp)) Text( @@ -99,8 +119,8 @@ fun EmptyView( style = TextStyle( color = Gray800, fontWeight = FontWeight(500), - fontSize = 15.sp - ) + fontSize = 15.sp, + ), ) } } @@ -109,7 +129,7 @@ fun EmptyView( private fun removeHistoryItem( viewState: State, removeItem: String, - viewModel: SearchViewModel + viewModel: SearchViewModel, ) { val historyItems = viewState.value.searchHistoryItems val removedHistoryItems = historyItems.filterNot { it == removeItem } @@ -119,5 +139,7 @@ private fun removeHistoryItem( @Preview @Composable fun PreviewSearchScreen() { - SearchScreen() + SearchScreen( + navController = NavController(LocalContext.current), + ) } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/search/SearchViewModel.kt b/presentation/src/main/java/com/everymeal/presentation/ui/search/SearchViewModel.kt index 9dc6c7dd..415ccd4a 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/search/SearchViewModel.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/search/SearchViewModel.kt @@ -1,11 +1,16 @@ package com.everymeal.presentation.ui.search +import androidx.lifecycle.viewModelScope +import com.everymeal.domain.repository.search.SearchRepository import com.everymeal.presentation.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class SearchViewModel @Inject constructor() : +class SearchViewModel @Inject constructor( + private val searchRepository: SearchRepository, +) : BaseViewModel(SearchState()) { init { updateState { @@ -15,21 +20,39 @@ class SearchViewModel @Inject constructor() : override fun handleEvents(event: SearchEvent) { when (event) { - is SearchEvent.SetShowSearchHistory -> { + is SearchEvent.SearchQueryChanged -> { updateState { - copy(searchIsShowHistory = event.show) + copy( + searchQuery = event.query, + searchIsShowHistory = event.query.isEmpty(), + ) } } - is SearchEvent.SearchQueryChanged -> { + is SearchEvent.UpdateSearchHistory -> { updateState { - copy(searchQuery = event.query) + copy(searchHistoryItems = event.historyItems) } } - is SearchEvent.UpdateSearchHistory -> { + is SearchEvent.SearchRestaurant -> { + search() + } + } + } + + private fun search() { + viewModelScope.launch { + val keyword = viewState.value.searchQuery + if (keyword.isEmpty()) { + return@launch + } + searchRepository.search(keyword).onSuccess { result -> updateState { - copy(searchHistoryItems = event.historyItems) + copy( + searchResultList = result, + searchIsShowHistory = false, + ) } } } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/search/history/SearchHistoryList.kt b/presentation/src/main/java/com/everymeal/presentation/ui/search/history/SearchHistoryList.kt index ac7a4942..0c7e60aa 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/search/history/SearchHistoryList.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/search/history/SearchHistoryList.kt @@ -14,7 +14,6 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -23,6 +22,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.everymeal.presentation.R import com.everymeal.presentation.ui.theme.Gray500 +import com.everymeal.presentation.ui.theme.Gray900 @Composable fun SearchHistoryList( @@ -30,9 +30,8 @@ fun SearchHistoryList( isVisible: Boolean, onHistoryItemClicked: (String) -> Unit, removeHistoryItem: (String) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { - if (isVisible) { Column(modifier = modifier) { Text( @@ -44,21 +43,26 @@ fun SearchHistoryList( ), modifier = Modifier .fillMaxWidth() - .padding(top = 30.dp, bottom = 10.dp) + .padding(top = 30.dp, bottom = 10.dp), ) LazyColumn( - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), ) { items(historyItems) { item -> Row( modifier = Modifier .fillMaxWidth() - .padding(vertical = 16.dp), - horizontalArrangement = Arrangement.SpaceBetween + .padding(vertical = 16.dp) + .clickable { onHistoryItemClicked(item) }, + horizontalArrangement = Arrangement.SpaceBetween, ) { Text( text = item, - modifier = Modifier.clickable { onHistoryItemClicked(item) } + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight(400), + color = Gray900 + ) ) Icon( imageVector = Icons.Default.Close, @@ -67,7 +71,7 @@ fun SearchHistoryList( .size(24.dp) .clickable { removeHistoryItem(item) - } + }, ) } } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/search/topbar/SearchTopBar.kt b/presentation/src/main/java/com/everymeal/presentation/ui/search/topbar/SearchTopBar.kt index e277d7ab..b0af7585 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/search/topbar/SearchTopBar.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/search/topbar/SearchTopBar.kt @@ -5,10 +5,10 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -17,32 +17,29 @@ import com.everymeal.presentation.R import com.everymeal.presentation.components.EveryMealTextField @Composable -fun TopBar( +fun SearchTopBar( modifier: Modifier = Modifier, searchQuery: String, - changeQuery: (String) -> Unit, - onBackClick: () -> Unit, - setShowHistory: (Boolean) -> Unit, + changeQuery: (String) -> Unit = {}, + onBackClick: () -> Unit = {}, + onSearchAction: () -> Unit = {} ) { Row( - modifier = modifier - .fillMaxWidth() - .padding(top = 14.dp) + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, ) { Image( - painter = painterResource(id = R.drawable.ic_launcher_foreground), + painter = painterResource(id = R.drawable.icon_arrow_back_mono), contentDescription = null, - modifier = modifier + modifier = Modifier .size(48.dp) .padding(12.dp) - .clickable { - onBackClick() - } + .clickable(onClick = onBackClick), ) SearchBar( searchQuery = searchQuery, changeQuery = changeQuery, - setShowHistory = setShowHistory, + onSearchAction = onSearchAction ) } } @@ -52,25 +49,24 @@ fun SearchBar( modifier: Modifier = Modifier, searchQuery: String, changeQuery: (String) -> Unit, - setShowHistory: (Boolean) -> Unit + onSearchAction: () -> Unit = {}, ) { Box(modifier = modifier) { EveryMealTextField( - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 56.dp), + modifier = Modifier.fillMaxWidth(), value = searchQuery, onValueChange = { changeQuery(it) - setShowHistory(it.isEmpty()) }, placeholderText = stringResource(R.string.placeholder_search), leadingIcon = { Image( painter = painterResource(id = R.drawable.icon_search_mono), - contentDescription = null + contentDescription = null, ) }, + maxLines = 1, + onEnterPressed = onSearchAction, ) } } diff --git a/presentation/src/main/res/drawable/img_review_searchbar.xml b/presentation/src/main/res/drawable/img_review_searchbar.xml new file mode 100644 index 00000000..bcf39c18 --- /dev/null +++ b/presentation/src/main/res/drawable/img_review_searchbar.xml @@ -0,0 +1,20 @@ + + + + + +