diff --git a/app/src/main/java/com/spoony/spoony/data/datasource/PlaceDataSource.kt b/app/src/main/java/com/spoony/spoony/data/datasource/PlaceDataSource.kt new file mode 100644 index 00000000..e0ea8bf0 --- /dev/null +++ b/app/src/main/java/com/spoony/spoony/data/datasource/PlaceDataSource.kt @@ -0,0 +1,8 @@ +package com.spoony.spoony.data.datasource + +import com.spoony.spoony.data.dto.base.BaseResponse +import com.spoony.spoony.data.dto.response.SearchPlaceResponseDto + +interface PlaceDataSource { + suspend fun getPlaces(query: String, display: Int): BaseResponse +} diff --git a/app/src/main/java/com/spoony/spoony/data/datasourceimpl/PlaceDataSourceImpl.kt b/app/src/main/java/com/spoony/spoony/data/datasourceimpl/PlaceDataSourceImpl.kt new file mode 100644 index 00000000..02fabf42 --- /dev/null +++ b/app/src/main/java/com/spoony/spoony/data/datasourceimpl/PlaceDataSourceImpl.kt @@ -0,0 +1,17 @@ +package com.spoony.spoony.data.datasourceimpl + +import com.spoony.spoony.data.datasource.PlaceDataSource +import com.spoony.spoony.data.dto.base.BaseResponse +import com.spoony.spoony.data.dto.response.SearchPlaceResponseDto +import com.spoony.spoony.data.service.PlaceService +import javax.inject.Inject + +class PlaceDataSourceImpl @Inject constructor( + private val placeService: PlaceService +) : PlaceDataSource { + override suspend fun getPlaces( + query: String, + display: Int + ): BaseResponse = + placeService.getPlaces(query, display) +} diff --git a/app/src/main/java/com/spoony/spoony/data/di/DataSourceModule.kt b/app/src/main/java/com/spoony/spoony/data/di/DataSourceModule.kt index e15647ae..95787c86 100644 --- a/app/src/main/java/com/spoony/spoony/data/di/DataSourceModule.kt +++ b/app/src/main/java/com/spoony/spoony/data/di/DataSourceModule.kt @@ -1,8 +1,10 @@ package com.spoony.spoony.data.di import com.spoony.spoony.data.datasource.DummyRemoteDataSource +import com.spoony.spoony.data.datasource.PlaceDataSource import com.spoony.spoony.data.datasource.PostRemoteDataSource import com.spoony.spoony.data.datasourceimpl.DummyRemoteDataSourceImpl +import com.spoony.spoony.data.datasourceimpl.PlaceDataSourceImpl import com.spoony.spoony.data.datasourceimpl.PostRemoteDataSourceImpl import dagger.Binds import dagger.Module @@ -20,4 +22,10 @@ abstract class DataSourceModule { @Binds @Singleton abstract fun bindPostDataSource(postRemoteDataSourceImpl: PostRemoteDataSourceImpl): PostRemoteDataSource + + @Binds + @Singleton + abstract fun bindPlaceDataSource( + placeDataSourceImpl: PlaceDataSourceImpl + ): PlaceDataSource } diff --git a/app/src/main/java/com/spoony/spoony/data/di/RepositoryModule.kt b/app/src/main/java/com/spoony/spoony/data/di/RepositoryModule.kt index b9b25745..3b453e98 100644 --- a/app/src/main/java/com/spoony/spoony/data/di/RepositoryModule.kt +++ b/app/src/main/java/com/spoony/spoony/data/di/RepositoryModule.kt @@ -37,5 +37,5 @@ abstract class RepositoryModule { @Binds @Singleton - abstract fun provideRegisterRepository(registerRepositoryImpl: RegisterRepositoryImpl): RegisterRepository + abstract fun bindRegisterRepository(registerRepositoryImpl: RegisterRepositoryImpl): RegisterRepository } diff --git a/app/src/main/java/com/spoony/spoony/data/di/ServiceModule.kt b/app/src/main/java/com/spoony/spoony/data/di/ServiceModule.kt index b74b4677..32c01959 100644 --- a/app/src/main/java/com/spoony/spoony/data/di/ServiceModule.kt +++ b/app/src/main/java/com/spoony/spoony/data/di/ServiceModule.kt @@ -1,6 +1,7 @@ package com.spoony.spoony.data.di import com.spoony.spoony.data.service.DummyService +import com.spoony.spoony.data.service.PlaceService import com.spoony.spoony.data.service.PostService import dagger.Module import dagger.Provides @@ -21,4 +22,9 @@ object ServiceModule { @Singleton fun providePostService(retrofit: Retrofit): PostService = retrofit.create(PostService::class.java) + + @Provides + @Singleton + fun providePlaceService(retrofit: Retrofit): PlaceService = + retrofit.create(PlaceService::class.java) } diff --git a/app/src/main/java/com/spoony/spoony/data/dto/response/PlaceResponseDto.kt b/app/src/main/java/com/spoony/spoony/data/dto/response/PlaceResponseDto.kt new file mode 100644 index 00000000..7142dd28 --- /dev/null +++ b/app/src/main/java/com/spoony/spoony/data/dto/response/PlaceResponseDto.kt @@ -0,0 +1,24 @@ +package com.spoony.spoony.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SearchPlaceResponseDto( + @SerialName("placeList") + val placeList: List +) + +@Serializable +data class PlaceResponseDto( + @SerialName("placeName") + val placeName: String, + @SerialName("placeAddress") + val placeAddress: String, + @SerialName("placeRoadAddress") + val placeRoadAddress: String, + @SerialName("latitude") + val latitude: Double, + @SerialName("longitude") + val longitude: Double +) diff --git a/app/src/main/java/com/spoony/spoony/data/mapper/PlaceMapper.kt b/app/src/main/java/com/spoony/spoony/data/mapper/PlaceMapper.kt new file mode 100644 index 00000000..c0e2e1be --- /dev/null +++ b/app/src/main/java/com/spoony/spoony/data/mapper/PlaceMapper.kt @@ -0,0 +1,13 @@ +package com.spoony.spoony.data.mapper + +import com.spoony.spoony.data.dto.response.PlaceResponseDto +import com.spoony.spoony.domain.entity.PlaceEntity + +fun PlaceResponseDto.toDomain(): PlaceEntity = + PlaceEntity( + placeName = placeName, + placeAddress = placeAddress, + placeRoadAddress = placeRoadAddress, + latitude = latitude, + longitude = longitude + ) diff --git a/app/src/main/java/com/spoony/spoony/data/repositoryimpl/RegisterRepositoryImpl.kt b/app/src/main/java/com/spoony/spoony/data/repositoryimpl/RegisterRepositoryImpl.kt index 0232c2be..85bc2689 100644 --- a/app/src/main/java/com/spoony/spoony/data/repositoryimpl/RegisterRepositoryImpl.kt +++ b/app/src/main/java/com/spoony/spoony/data/repositoryimpl/RegisterRepositoryImpl.kt @@ -1,98 +1,67 @@ package com.spoony.spoony.data.repositoryimpl import android.net.Uri +import com.spoony.spoony.data.datasource.PlaceDataSource +import com.spoony.spoony.data.mapper.toDomain +import com.spoony.spoony.domain.entity.CategoryEntity +import com.spoony.spoony.domain.entity.PlaceEntity import com.spoony.spoony.domain.repository.RegisterRepository -import com.spoony.spoony.presentation.register.model.Category -import com.spoony.spoony.presentation.register.model.Place import javax.inject.Inject -class RegisterRepositoryImpl @Inject constructor() : RegisterRepository { - override suspend fun getCategories(): Result> = Result.success( +class RegisterRepositoryImpl @Inject constructor( + private val placeDataSource: PlaceDataSource +) : RegisterRepository { + override suspend fun getCategories(): Result> = Result.success( listOf( - Category( + CategoryEntity( categoryId = 2, categoryName = "한식", - iconUrlSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", - iconUrlNotSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" + iconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", + unSelectedIconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" ), - Category( + CategoryEntity( categoryId = 3, categoryName = "일식", - iconUrlSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", - iconUrlNotSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" + iconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", + unSelectedIconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" ), - Category( + CategoryEntity( categoryId = 4, categoryName = "중식", - iconUrlSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", - iconUrlNotSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" + iconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", + unSelectedIconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" ), - Category( + CategoryEntity( categoryId = 5, categoryName = "양식", - iconUrlSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", - iconUrlNotSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" + iconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", + unSelectedIconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" ), - Category( + CategoryEntity( categoryId = 6, categoryName = "퓨전/세계요리", - iconUrlSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", - iconUrlNotSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" + iconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", + unSelectedIconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" ), - Category( + CategoryEntity( categoryId = 7, categoryName = "카페", - iconUrlSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", - iconUrlNotSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" + iconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", + unSelectedIconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" ), - Category( + CategoryEntity( categoryId = 8, categoryName = "주류", - iconUrlSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", - iconUrlNotSelected = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" + iconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_white.png", + unSelectedIconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/korean_black.png" ) ) ) - override suspend fun searchPlace(query: String, display: Int): Result> = Result.success( - listOf( - Place( - placeName = "스타벅스 강남대로점", - placeAddress = "서울특별시 서초구 서초동 1305-7", - placeRoadAddress = "서울특별시 서초구 강남대로 369 (서초동)", - latitude = 37.4979, - longitude = 127.0276 - ), - Place( - placeName = "스타벅스 신촌역점", - placeAddress = "서울특별시 서대문구 창천동 30-1", - placeRoadAddress = "서울특별시 서대문구 연세로 10-1 (창천동)", - latitude = 37.5598, - longitude = 126.9423 - ), - Place( - placeName = "스타벅스 홍대역점", - placeAddress = "서울특별시 마포구 서교동 358-11", - placeRoadAddress = "서울특별시 마포구 양화로 166 (서교동)", - latitude = 37.5569, - longitude = 126.9237 - ), - Place( - placeName = "스타벅스 부산센텀시티점", - placeAddress = "부산광역시 해운대구 우동 1505", - placeRoadAddress = "부산광역시 해운대구 센텀남대로 35 (우동)", - latitude = 35.1698, - longitude = 129.1315 - ), - Place( - placeName = "스타벅스 대구동성로점", - placeAddress = "대구광역시 중구 동성로3가 11", - placeRoadAddress = "대구광역시 중구 동성로 55 (동성로3가)", - latitude = 35.8703, - longitude = 128.5978 - ) - ) - ) + override suspend fun searchPlace(query: String, display: Int): Result> = runCatching { + placeDataSource.getPlaces(query, display).data!!.placeList + .map { it.toDomain() } + } override suspend fun checkDuplicatePlace( userId: Long, diff --git a/app/src/main/java/com/spoony/spoony/data/service/PlaceService.kt b/app/src/main/java/com/spoony/spoony/data/service/PlaceService.kt new file mode 100644 index 00000000..44bfa04c --- /dev/null +++ b/app/src/main/java/com/spoony/spoony/data/service/PlaceService.kt @@ -0,0 +1,14 @@ +package com.spoony.spoony.data.service + +import com.spoony.spoony.data.dto.base.BaseResponse +import com.spoony.spoony.data.dto.response.SearchPlaceResponseDto +import retrofit2.http.GET +import retrofit2.http.Query + +interface PlaceService { + @GET("api/v1/place/search") + suspend fun getPlaces( + @Query("query") query: String, + @Query("display") display: Int = 5 + ): BaseResponse +} diff --git a/app/src/main/java/com/spoony/spoony/domain/entity/PlaceEntity.kt b/app/src/main/java/com/spoony/spoony/domain/entity/PlaceEntity.kt new file mode 100644 index 00000000..ea586f38 --- /dev/null +++ b/app/src/main/java/com/spoony/spoony/domain/entity/PlaceEntity.kt @@ -0,0 +1,9 @@ +package com.spoony.spoony.domain.entity + +data class PlaceEntity( + val placeName: String, + val placeAddress: String, + val placeRoadAddress: String, + val latitude: Double, + val longitude: Double +) diff --git a/app/src/main/java/com/spoony/spoony/domain/repository/RegisterRepository.kt b/app/src/main/java/com/spoony/spoony/domain/repository/RegisterRepository.kt index 83dfa42c..e267767b 100644 --- a/app/src/main/java/com/spoony/spoony/domain/repository/RegisterRepository.kt +++ b/app/src/main/java/com/spoony/spoony/domain/repository/RegisterRepository.kt @@ -1,20 +1,17 @@ package com.spoony.spoony.domain.repository import android.net.Uri -import com.spoony.spoony.presentation.register.model.Category -import com.spoony.spoony.presentation.register.model.Place +import com.spoony.spoony.domain.entity.CategoryEntity +import com.spoony.spoony.domain.entity.PlaceEntity interface RegisterRepository { - suspend fun getCategories(): Result> - - suspend fun searchPlace(query: String, display: Int = 5): Result> - + suspend fun getCategories(): Result> + suspend fun searchPlace(query: String, display: Int = 5): Result> suspend fun checkDuplicatePlace( userId: Long, latitude: Double, longitude: Double ): Result - suspend fun registerPost( userId: Long, title: String, diff --git a/app/src/main/java/com/spoony/spoony/presentation/register/RegisterViewModel.kt b/app/src/main/java/com/spoony/spoony/presentation/register/RegisterViewModel.kt index 1c7928c4..3db48ecc 100644 --- a/app/src/main/java/com/spoony/spoony/presentation/register/RegisterViewModel.kt +++ b/app/src/main/java/com/spoony/spoony/presentation/register/RegisterViewModel.kt @@ -2,6 +2,8 @@ package com.spoony.spoony.presentation.register import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.spoony.spoony.domain.entity.CategoryEntity +import com.spoony.spoony.domain.entity.PlaceEntity import com.spoony.spoony.domain.repository.RegisterRepository import com.spoony.spoony.presentation.register.component.SelectedPhoto import com.spoony.spoony.presentation.register.model.Category @@ -38,10 +40,12 @@ class RegisterViewModel @Inject constructor( private fun loadCategories() { viewModelScope.launch { repository.getCategories() - .onSuccess { categories -> - _state.update { it.copy(categories = categories.toImmutableList()) } - } - .onFailure { + .onSuccess { categoryEntities -> + _state.update { state -> + state.copy( + categories = categoryEntities.map { it.toPresentation() }.toImmutableList() + ) + } } } } @@ -51,26 +55,17 @@ class RegisterViewModel @Inject constructor( } fun searchPlace(query: String) { - if (query.isBlank()) { - _state.update { it.copy(searchResults = persistentListOf()) } - return - } - viewModelScope.launch { - _state.update { it.copy(isSearching = true) } - repository.searchPlace(query) - .onSuccess { places -> + .onSuccess { placeEntities -> _state.update { it.copy( - searchResults = places.toImmutableList(), - isSearching = false + searchResults = placeEntities.map { entity -> + entity.toPresentation() + }.toImmutableList() ) } } - .onFailure { - _state.update { it.copy(isSearching = false) } - } } } @@ -81,7 +76,7 @@ class RegisterViewModel @Inject constructor( fun selectPlace(place: Place) { viewModelScope.launch { repository.checkDuplicatePlace( - userId = 1L, // TODO: 실제 사용자 ID로 변경 + userId = 1L, latitude = place.latitude, longitude = place.longitude ).onSuccess { isDuplicate -> @@ -243,6 +238,23 @@ class RegisterViewModel @Inject constructor( } } +fun CategoryEntity.toPresentation(): Category = + Category( + categoryId = categoryId, + categoryName = categoryName, + iconUrlSelected = iconUrl, + iconUrlNotSelected = unSelectedIconUrl ?: iconUrl + ) + +fun PlaceEntity.toPresentation(): Place = + Place( + placeName = placeName, + placeAddress = placeAddress, + placeRoadAddress = placeRoadAddress, + latitude = latitude, + longitude = longitude + ) + sealed class RegisterSideEffect { data class ShowSnackbar(val message: String) : RegisterSideEffect() }