Skip to content

Commit

Permalink
Merge pull request #106 from TeamPINGLE/feat-plan-location-api
Browse files Browse the repository at this point in the history
[feat] 장소 검색 API 연동
  • Loading branch information
HAJIEUN02 authored Jan 11, 2024
2 parents 1bcb0e0 + a047539 commit a509929
Show file tree
Hide file tree
Showing 22 changed files with 244 additions and 77 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.sopt.pingle.data.datasource.remote

import org.sopt.pingle.data.model.remote.response.ResponsePlanDto
import org.sopt.pingle.util.base.BaseResponse

interface PlanRemoteDataSource {
suspend fun getPlanLocation(searchWord: String): BaseResponse<List<ResponsePlanDto>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.sopt.pingle.data.datasourceimpl.remote

import javax.inject.Inject
import org.sopt.pingle.data.datasource.remote.PlanRemoteDataSource
import org.sopt.pingle.data.model.remote.response.ResponsePlanDto
import org.sopt.pingle.data.service.PlanService
import org.sopt.pingle.util.base.BaseResponse

class PlanRemoteDataSourceImpl @Inject constructor(
private val planService: PlanService
) : PlanRemoteDataSource {
override suspend fun getPlanLocation(searchWord: String): BaseResponse<List<ResponsePlanDto>> =
planService.getPlanLocationList(searchWord = searchWord)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.sopt.pingle.data.model.remote.request

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class RequestPlanDto(
@SerialName("search")
val search: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.sopt.pingle.data.model.remote.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.sopt.pingle.domain.model.PlanLocationEntity

@Serializable
data class ResponsePlanDto(
@SerialName("x")
val xCoordinate: Double,
@SerialName("y")
val yCoordinate: Double,
@SerialName("location")
val locationName: String,
@SerialName("address")
val locationAddress: String,
@SerialName("roadAddress")
val locationRoadAddress: String
) {
fun toPlanLocationEntity() = PlanLocationEntity(
location = locationName,
address = locationAddress,
x = xCoordinate,
y = yCoordinate
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.sopt.pingle.data.repository

import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import org.sopt.pingle.data.datasource.remote.PlanRemoteDataSource
import org.sopt.pingle.domain.model.PlanLocationEntity
import org.sopt.pingle.domain.repository.PlanRepository

class PlanRepositoryImpl @Inject constructor(
private val planRemoteDataSource: PlanRemoteDataSource
) : PlanRepository {
override suspend fun getPlanLocationList(searchWord: String): Flow<List<PlanLocationEntity>> =
flow {
val result = runCatching {
planRemoteDataSource.getPlanLocation(searchWord = searchWord).data.map { planLocation ->
planLocation.toPlanLocationEntity()
}
}
emit(result.getOrThrow())
}
}
19 changes: 19 additions & 0 deletions app/src/main/java/org/sopt/pingle/data/service/PlanService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.sopt.pingle.data.service

import org.sopt.pingle.data.model.remote.response.ResponsePlanDto
import org.sopt.pingle.util.base.BaseResponse
import retrofit2.http.GET
import retrofit2.http.Query

interface PlanService {
@GET("$VERSION/$LOCATION")
suspend fun getPlanLocationList(
@Query(SEARCH_WORD) searchWord: String
): BaseResponse<List<ResponsePlanDto>>

companion object {
const val VERSION = "v1"
const val LOCATION = "location"
const val SEARCH_WORD = "search"
}
}
6 changes: 6 additions & 0 deletions app/src/main/java/org/sopt/pingle/di/DataSourceModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import org.sopt.pingle.data.datasource.remote.AuthRemoteDataSource
import org.sopt.pingle.data.datasource.remote.DummyRemoteDataSource
import org.sopt.pingle.data.datasource.remote.MapRemoteDataSource
import org.sopt.pingle.data.datasource.remote.PingleRemoteDataSource
import org.sopt.pingle.data.datasource.remote.PlanRemoteDataSource
import org.sopt.pingle.data.datasourceimpl.local.DummyLocalDataSourceImpl
import org.sopt.pingle.data.datasourceimpl.local.PingleLocalDataSourceImpl
import org.sopt.pingle.data.datasourceimpl.remote.AuthRemoteDataSourceImpl
import org.sopt.pingle.data.datasourceimpl.remote.DummyRemoteDataSourceImpl
import org.sopt.pingle.data.datasourceimpl.remote.MapRemoteDataSourceImpl
import org.sopt.pingle.data.datasourceimpl.remote.PingleRemoteDataSourceImpl
import org.sopt.pingle.data.datasourceimpl.remote.PlanRemoteDataSourceImpl

@Module
@InstallIn(SingletonComponent::class)
Expand Down Expand Up @@ -44,4 +46,8 @@ abstract class DataSourceModule {
@Binds
@Singleton
abstract fun bindsPingleLocalDataSource(pingleLocalDataSourceImpl: PingleLocalDataSourceImpl): PingleLocalDataSource

@Binds
@Singleton
abstract fun bindsPlanRemoteDataSource(planRemoteDataSourceImpl: PlanRemoteDataSourceImpl): PlanRemoteDataSource
}
6 changes: 6 additions & 0 deletions app/src/main/java/org/sopt/pingle/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import org.sopt.pingle.data.repository.AuthRepositoryImpl
import org.sopt.pingle.data.repository.DummyRepositoryImpl
import org.sopt.pingle.data.repository.MapRepositoryImpl
import org.sopt.pingle.data.repository.PingleRepositoryImpl
import org.sopt.pingle.data.repository.PlanRepositoryImpl
import org.sopt.pingle.domain.repository.AuthRepository
import org.sopt.pingle.domain.repository.DummyRepository
import org.sopt.pingle.domain.repository.MapRepository
import org.sopt.pingle.domain.repository.PingleRepository
import org.sopt.pingle.domain.repository.PlanRepository

@Module
@InstallIn(SingletonComponent::class)
Expand All @@ -32,4 +34,8 @@ abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindsPingleRepository(pingleRepositoryImpl: PingleRepositoryImpl): PingleRepository

@Binds
@Singleton
abstract fun bindsPlanRepository(planRepositoryImpl: PlanRepositoryImpl): PlanRepository
}
6 changes: 6 additions & 0 deletions app/src/main/java/org/sopt/pingle/di/ServiceModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.sopt.pingle.data.service.AuthService
import org.sopt.pingle.data.service.DummyService
import org.sopt.pingle.data.service.MapService
import org.sopt.pingle.data.service.PingleService
import org.sopt.pingle.data.service.PlanService
import org.sopt.pingle.di.qualifier.Pingle
import retrofit2.Retrofit
import retrofit2.create
Expand All @@ -35,4 +36,9 @@ object ServiceModule {
@Singleton
fun providesPingleService(@Pingle retrofit: Retrofit): PingleService =
retrofit.create(PingleService::class.java)

@Provides
@Singleton
fun providesPlanService(@Pingle retrofit: Retrofit): PlanService =
retrofit.create(PlanService::class.java)
}
7 changes: 7 additions & 0 deletions app/src/main/java/org/sopt/pingle/di/UseCaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import javax.inject.Singleton
import org.sopt.pingle.domain.repository.DummyRepository
import org.sopt.pingle.domain.repository.MapRepository
import org.sopt.pingle.domain.repository.PingleRepository
import org.sopt.pingle.domain.repository.PlanRepository
import org.sopt.pingle.domain.usecase.GetDummyUserListUseCase
import org.sopt.pingle.domain.usecase.GetPinListWithoutFilteringUseCase
import org.sopt.pingle.domain.usecase.GetPingleListUseCase
import org.sopt.pingle.domain.usecase.GetPlanLocationListUseCase
import org.sopt.pingle.domain.usecase.PostPingleCancelUseCase
import org.sopt.pingle.domain.usecase.PostPingleJoinUseCase
import org.sopt.pingle.domain.usecase.SetDummyDataUseCase
Expand Down Expand Up @@ -47,4 +49,9 @@ class UseCaseModule {
@Singleton
fun providesPostPingleCancelUseCase(pingleRepository: PingleRepository): PostPingleCancelUseCase =
PostPingleCancelUseCase(pingleRepository = pingleRepository)

@Provides
@Singleton
fun providesGetPlanLocationListUseCase(planRepository: PlanRepository): GetPlanLocationListUseCase =
GetPlanLocationListUseCase(planRepository = planRepository)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.sopt.pingle.domain.repository

import kotlinx.coroutines.flow.Flow
import org.sopt.pingle.domain.model.PlanLocationEntity

interface PlanRepository {
suspend fun getPlanLocationList(searchWord: String): Flow<List<PlanLocationEntity>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.sopt.pingle.domain.usecase

import kotlinx.coroutines.flow.Flow
import org.sopt.pingle.domain.model.PlanLocationEntity
import org.sopt.pingle.domain.repository.PlanRepository

class GetPlanLocationListUseCase(
private val planRepository: PlanRepository
) {
suspend operator fun invoke(searchWord: String): Flow<List<PlanLocationEntity>> =
planRepository.getPlanLocationList(searchWord = searchWord)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.sopt.pingle.presentation.model

import androidx.databinding.ObservableBoolean
import org.sopt.pingle.domain.model.PlanLocationEntity

data class PlanLocationModel(
val planLocation: PlanLocationEntity,
var isSelected: ObservableBoolean = ObservableBoolean(false)
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.widget.ViewPager2
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.sopt.pingle.R
Expand All @@ -21,6 +22,7 @@ import org.sopt.pingle.presentation.ui.main.plan.plantitle.PlanTitleFragment
import org.sopt.pingle.util.base.BindingActivity
import org.sopt.pingle.util.component.AllModalDialogFragment

@AndroidEntryPoint
class PlanActivity : BindingActivity<ActivityPlanBinding>(R.layout.activity_plan) {
private val planViewModel: PlanViewModel by viewModels()
private lateinit var fragmentList: ArrayList<Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@ package org.sopt.pingle.presentation.ui.main.plan

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.sopt.pingle.domain.model.PlanLocationEntity
import org.sopt.pingle.domain.usecase.GetPlanLocationListUseCase
import org.sopt.pingle.presentation.type.CategoryType
import org.sopt.pingle.presentation.type.PlanType
import org.sopt.pingle.util.combineAll
import org.sopt.pingle.util.view.UiState

class PlanViewModel : ViewModel() {
@HiltViewModel
class PlanViewModel @Inject constructor(
private val getPlanLocationListUseCase: GetPlanLocationListUseCase
) : ViewModel() {
private val _currentPage = MutableStateFlow(FIRST_PAGE_POSITION)
val currentPage get() = _currentPage.asStateFlow()
val planTitle = MutableStateFlow("")
Expand All @@ -29,15 +40,22 @@ class PlanViewModel : ViewModel() {
val planOpenChattingLink = MutableStateFlow("")
val planSummary = MutableStateFlow("")

private val _selectedLocation = MutableStateFlow<PlanLocationEntity?>(null)
val selectedLocation get() = _selectedLocation.asStateFlow()

private val _selectedCategory = MutableStateFlow<CategoryType?>(null)
val selectedCategory get() = _selectedCategory.asStateFlow()

private val _selectedRecruitment = MutableStateFlow<String?>("1")
val selectedRecruitment get() = _selectedRecruitment.asStateFlow()

private val _planLocationListState =
MutableSharedFlow<UiState<List<PlanLocationEntity>>>()
val planLocationListState get() = _planLocationListState.asSharedFlow()

private val _planLocationList = MutableStateFlow<List<PlanLocationEntity>>(emptyList())
val planLocationList get() = _planLocationList.asStateFlow()

private val _selectedLocation = MutableStateFlow<PlanLocationEntity?>(null)
val selectedLocation get() = _selectedLocation.asStateFlow()

val isPlanBtnEnabled: StateFlow<Boolean> = listOf(
currentPage,
selectedCategory,
Expand Down Expand Up @@ -112,8 +130,29 @@ class PlanViewModel : ViewModel() {

private var oldPosition = DEFAULT_OLD_POSITION

private val _planLocationList = MutableStateFlow<List<PlanLocationEntity>>(emptyList())
val planLocationList get() = _planLocationList.asStateFlow()
fun getPlanLocationList(searchWord: String) {
viewModelScope.launch {
_planLocationListState.emit(UiState.Loading)
_planLocationList.value = emptyList()
_selectedLocation.value = null
runCatching {
getPlanLocationListUseCase(searchWord).collect() { planLocationList ->
when (planLocationList.isEmpty()) {
true -> {
_planLocationListState.emit(UiState.Empty)
}

false -> {
_planLocationList.value = planLocationList
_planLocationListState.emit(UiState.Success(planLocationList))
}
}
}
}.onFailure { exception: Throwable ->
_planLocationListState.emit(UiState.Error(exception.message))
}
}
}

fun updatePlanLocationList(position: Int) {
when (oldPosition) {
Expand All @@ -131,63 +170,17 @@ class PlanViewModel : ViewModel() {
setIsSelected(position)
}
}
_selectedLocation.value = if (getIsSelected(position)) _planLocationList.value[position] else null
_selectedLocation.value =
if (getIsSelected(position)) _planLocationList.value[position] else null
oldPosition = position
}

// 이전 값이 -> 초기값 + 셀렉티드 값이 있으면
fun checkIsNull(): Boolean {
return _planLocationList.value.isEmpty()
// TODO return planLocationList.value.isEmpty()
}

private fun setIsSelected(position: Int) {
_planLocationList.value[position].isSelected.set(!_planLocationList.value[position].isSelected.get())
}

private fun getIsSelected(position: Int) = _planLocationList.value[position].isSelected.get()

init {
_planLocationList.value = listOf(
PlanLocationEntity(
location = "하얀집",
address = "서울 중구 퇴계로6길 12",
x = 123.5,
y = 56.7
),
PlanLocationEntity(
location = "하얀집2호점",
address = "서울 중구 퇴계로6길 12",
x = 123.5,
y = 56.7
),
PlanLocationEntity(
location = "하얀집3호점",
address = "서울 중구 퇴계로6길 12",
x = 123.5,
y = 56.7
),
PlanLocationEntity(
location = "하얀집 싫어싫어싫어",
address = "서울 중구 퇴계로6길 12",
x = 123.5,
y = 56.7
),
PlanLocationEntity(
location = "하얀집 좋아좋아좋아",
address = "서울 중구 퇴계로6길 12",
x = 123.5,
y = 56.7
),
PlanLocationEntity(
location = "하얀집웅시러",
address = "서울 중구 퇴계로6길 12",
x = 123.5,
y = 56.7
)
)
}

companion object {
const val FIRST_PAGE_POSITION = 0
const val DEFAULT_OLD_POSITION = -1
Expand Down
Loading

0 comments on commit a509929

Please sign in to comment.