Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] 핀 클릭 시 번개 목록 API 연동 #101

Merged
merged 12 commits into from
Jan 11, 2024
Merged
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
tools:ignore="LockedOrientationActivity" />
<activity
android:name=".presentation.ui.main.MainActivity"
android:exported="true"
android:exported="false"
android:screenOrientation="portrait"
tools:ignore="LockedOrientationActivity" />
<activity
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package org.sopt.pingle.data.datasource.remote

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

interface MapRemoteDataSource {
suspend fun getPinListWithoutFiltering(teamId: Long, category: String?): BaseResponse<List<ResponsePinListDto>>
suspend fun getPinListWithoutFiltering(
teamId: Long,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

근데 생각해보니까 아요가 Long 타입 없다고 하지 않았었나..?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오잉 이거 서버에서 long으로 넘어오던뎅,, 그리고 아요 이미 서버 다 붙이긴 함요,,, 지도 부분

category: String?
): BaseResponse<List<ResponsePinListDto>>

suspend fun getPingleList(teamId: Long, pinId: Long): BaseResponse<List<ResponsePingleListDto>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.sopt.pingle.data.datasourceimpl.remote
import javax.inject.Inject
import org.sopt.pingle.data.datasource.remote.MapRemoteDataSource
import org.sopt.pingle.data.model.remote.response.ResponsePinListDto
import org.sopt.pingle.data.model.remote.response.ResponsePingleListDto
import org.sopt.pingle.data.service.MapService
import org.sopt.pingle.util.base.BaseResponse

Expand All @@ -14,4 +15,10 @@ class MapRemoteDataSourceImpl @Inject constructor(
category: String?
): BaseResponse<List<ResponsePinListDto>> =
mapService.getPinListWithoutFiltering(teamId = teamId, category = category)

override suspend fun getPingleList(
teamId: Long,
pinId: Long
): BaseResponse<List<ResponsePingleListDto>> =
mapService.getPingleList(teamId = teamId, pinId = pinId)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.sopt.pingle.data.model.remote.response

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

@Serializable
data class ResponsePingleListDto(
@SerialName("id")
val id: Long,
@SerialName("category")
val category: String,
@SerialName("name")
val name: String,
@SerialName("ownerName")
val ownerName: String,
@SerialName("location")
val location: String,
@SerialName("date")
val date: String,
@SerialName("startAt")
val startAt: String,
@SerialName("endAt")
val endAt: String,
@SerialName("maxParticipants")
val maxParticipants: Int,
@SerialName("curParticipants")
val curParticipants: Int,
@SerialName("isParticipating")
val isParticipating: Boolean,
@SerialName("chatLink")
val chatLink: String
) {
fun toPingleEntity() = PingleEntity(
id = this.id,
category = this.category,
name = this.name,
ownerName = this.ownerName,
location = this.location,
date = this.date,
startAt = this.startAt,
endAt = this.endAt,
maxParticipants = this.maxParticipants,
curParticipants = this.curParticipants,
isParticipating = this.isParticipating,
chatLink = this.chatLink
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import org.sopt.pingle.data.datasource.remote.MapRemoteDataSource
import org.sopt.pingle.domain.model.PinEntity
import org.sopt.pingle.domain.model.PingleEntity
import org.sopt.pingle.domain.repository.MapRepository

class MapRepositoryImpl @Inject constructor(
Expand All @@ -24,4 +25,16 @@ class MapRepositoryImpl @Inject constructor(
}
emit(result.getOrThrow())
}

override suspend fun getPingleList(teamId: Long, pinId: Long): Flow<List<PingleEntity>> = flow {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flow로 한 이유가 있나용?!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flow로 방출한다면 이 루틴은 죽는 시점이 언제인가요?

Copy link
Collaborator Author

@jihyunniiii jihyunniiii Jan 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 반환되는 플로우는 viewModelScope.launch 블록 내부에서 수집되게 됩니당. 그렇기 때문에 뷰모델이 소멸될 때 코루틴도 함께 취소되고 코루틴이 취소되면서 그 안에서 실행되고 있는 플로우도 자동으로 취소됩니다 즉, 뷰모델이 죽으면 플로우도 같이 죽어용 ㅋ
플로우로 한 이유는,, 플로우 한 번 써보고 싶어서요,,

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

근데 이 루틴이 해당 블록 내에서 수행되는게 아니지 않나용?? 방출된게 수집되는 것이지

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음,, 제가 질문을 제대로 이해한 게 맞는지 모르겠지만,, 이 함수의 결과값으로 플로우를 반환해주는 것이기 때문에 뷰모델 스코프 내부에서 플로우가 생성되고 종료되는 거라구 생각했습니당,,,

val result = runCatching {
mapDataSource.getPingleList(
teamId = teamId,
pinId = pinId
).data.map { pingle ->
pingle.toPingleEntity()
}
}
emit(result.getOrThrow())
}
}
11 changes: 10 additions & 1 deletion app/src/main/java/org/sopt/pingle/data/service/MapService.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.sopt.pingle.data.service

import org.sopt.pingle.data.model.remote.response.ResponsePinListDto
import org.sopt.pingle.data.model.remote.response.ResponsePingleListDto
import org.sopt.pingle.util.base.BaseResponse
import retrofit2.http.GET
import retrofit2.http.Path
Expand All @@ -13,11 +14,19 @@ interface MapService {
@Query(CATEGORY) category: String?
): BaseResponse<List<ResponsePinListDto>>

@GET("$VERSION/$TEAMS/{$TEAM_ID}/$PINS/{$PIN_ID}/$MEETINGS")
suspend fun getPingleList(
@Path("$TEAM_ID") teamId: Long,
@Path("$PIN_ID") pinId: Long
): BaseResponse<List<ResponsePingleListDto>>

companion object {
const val VERSION = "v1"
const val TEAMS = "teams"
const val PINS = "pins"
const val TEAM_ID = "teamId"
const val PINS = "pins"
const val PIN_ID = "pinId"
const val CATEGORY = "category"
const val MEETINGS = "meetings"
}
}
6 changes: 6 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 @@ -9,6 +9,7 @@ import org.sopt.pingle.domain.repository.DummyRepository
import org.sopt.pingle.domain.repository.MapRepository
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.SetDummyDataUseCase

@Module
Expand All @@ -28,4 +29,9 @@ class UseCaseModule {
@Singleton
fun providesGetPinListWithoutFilteringUseCase(mapRepository: MapRepository): GetPinListWithoutFilteringUseCase =
GetPinListWithoutFilteringUseCase(mapRepository = mapRepository)

@Provides
@Singleton
fun providesGetPingleListUseCase(mapRepository: MapRepository): GetPingleListUseCase =
GetPingleListUseCase(mapRepository = mapRepository)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package org.sopt.pingle.domain.repository

import kotlinx.coroutines.flow.Flow
import org.sopt.pingle.domain.model.PinEntity
import org.sopt.pingle.domain.model.PingleEntity

interface MapRepository {
suspend fun getPinListWithoutFiltering(teamId: Long, category: String?): Flow<List<PinEntity>>
suspend fun getPingleList(teamId: Long, pinId: Long): Flow<List<PingleEntity>>
}
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.PingleEntity
import org.sopt.pingle.domain.repository.MapRepository

class GetPingleListUseCase(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거랑 withOut..UseCase를 나눈 이유가 뭘까요?!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유즈케이스가 하나의 책임을 가지는 것이 좋다고 생각해서 나누었습니다

private val mapRepository: MapRepository
) {
suspend operator fun invoke(teamId: Long, pinId: Long): Flow<List<PingleEntity>> =
mapRepository.getPingleList(teamId = teamId, pinId = pinId)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package org.sopt.pingle.presentation.mapper
import androidx.databinding.Observable
import com.naver.maps.geometry.LatLng
import com.naver.maps.map.overlay.Marker
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import org.sopt.pingle.domain.model.PinEntity
import org.sopt.pingle.domain.model.PingleEntity
import org.sopt.pingle.presentation.model.MarkerModel
import org.sopt.pingle.presentation.type.CategoryType

Expand All @@ -29,3 +33,17 @@ fun PinEntity.toMarkerModel(): MarkerModel {

return markerModel
}

fun PingleEntity.isCompleted() = maxParticipants == curParticipants

fun PingleEntity.convertToCalenderDetail(): String {
val localDate = LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE)
val startTime = LocalTime.parse(startAt, DateTimeFormatter.ISO_LOCAL_TIME)
val endTime = LocalTime.parse(endAt, DateTimeFormatter.ISO_LOCAL_TIME)

return buildString {
append("${localDate.year}년 ${localDate.monthValue}월 ${localDate.dayOfMonth}일\n")
append("${startTime.format(DateTimeFormatter.ofPattern("HH:mm"))} ~ ")
append("${endTime.format(DateTimeFormatter.ofPattern("HH:mm"))}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.sopt.pingle.R
import org.sopt.pingle.databinding.FragmentMapBinding
import org.sopt.pingle.presentation.model.MarkerModel
import org.sopt.pingle.domain.model.PinEntity
import org.sopt.pingle.presentation.mapper.toMarkerModel
import org.sopt.pingle.presentation.type.CategoryType
import org.sopt.pingle.presentation.ui.main.home.mainlist.MainListFragment
import org.sopt.pingle.util.base.BindingFragment
import org.sopt.pingle.util.component.AllModalDialogFragment
import org.sopt.pingle.util.component.OnPingleCardClickListener
import org.sopt.pingle.util.component.PingleChip
import org.sopt.pingle.util.fragment.navigateToFragment
import org.sopt.pingle.util.fragment.navigateToWebView
import org.sopt.pingle.util.fragment.stringOf
import org.sopt.pingle.util.view.UiState

Expand Down Expand Up @@ -70,6 +71,7 @@ class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map),
with(uiSettings) {
isZoomControlEnabled = false
isScaleBarEnabled = false
isCompassEnabled = false
}

setOnMapClickListener { _, _ ->
Expand Down Expand Up @@ -100,15 +102,6 @@ class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map),
chipMapCategoryStudy.setChipCategoryType(CategoryType.STUDY)
chipMapCategoryMulti.setChipCategoryType(CategoryType.MULTI)
chipMapCategoryOthers.setChipCategoryType(CategoryType.OTHERS)
cardMap.listener = object : OnPingleCardClickListener {
override fun onPingleCardChatBtnClickListener() {
// TODO 선택된 마커로 웹뷰 연결
}

override fun onPingleCardParticipateBtnClickListener() {
// TODO 선택된 마커 참여 현황 여부에 따른 모달 로직 구현
}
}
}
}

Expand Down Expand Up @@ -143,11 +136,18 @@ class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map),
mapViewModel.getPinListWithoutFilter()
}.launchIn(lifecycleScope)

mapViewModel.markerListState.flowWithLifecycle(lifecycle).onEach { uiState ->
mapViewModel.pinEntityListState.flowWithLifecycle(lifecycle).onEach { uiState ->
when (uiState) {
is UiState.Success -> {
if (::naverMap.isInitialized) {
makeMarkers(uiState.data)
with(binding) {
fabMapHere.visibility = View.VISIBLE
fabMapList.visibility = View.VISIBLE
cardMap.visibility = View.INVISIBLE
}

mapViewModel.clearSelectedMarkerPosition()
}
}

Expand All @@ -165,6 +165,21 @@ class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map),
}
}
}.launchIn(lifecycleScope)

mapViewModel.pingleListState.flowWithLifecycle(lifecycle).onEach { uiState ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 emit 방식으로 한거면 flowWithLifeCylce 한 번 확인해보는게 좋을거 같습니당!! 물론 저희는 여러개의 루틴이 생길일이 있을 거 같진 않지만... 여러개의 루틴이 생기게 됐을때 최신값을 우선적으로 방출하는 아이가 맞는지 한 번 확인해주세용

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 확인안해도 될 거 같습니다 그대로 stateFLow군요 ㅋㅋ

when (uiState) {
is UiState.Success -> {
with(binding.cardMap) {
initLayout(uiState.data[SINGLE_SELECTION])
setOnChatButtonClick {
startActivity(navigateToWebView(uiState.data[SINGLE_SELECTION].chatLink))
}
}
}

else -> Unit
}
}.launchIn(lifecycleScope)
}

private fun setLocationTrackingMode() {
Expand Down Expand Up @@ -203,17 +218,21 @@ class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map),
}
}

private fun makeMarkers(markerList: List<MarkerModel>) {
private fun makeMarkers(pinEntityList: List<PinEntity>) {
mapViewModel.clearMarkerList()

markerList.mapIndexed { _, markerModel ->
markerModel.marker.apply {
mapViewModel.addMarkerList(this)
map = naverMap
setOnClickListener {
// TODO 마커 클릭 이벤트 수정
return@setOnClickListener true
pinEntityList.mapIndexed { index, pinEntity ->
pinEntity.toMarkerModel().apply {
this.marker.apply {
map = naverMap
setOnClickListener {
mapViewModel.updateMarkerModelListSelectedValue(index)
mapViewModel.getPingleList(pinEntity.id)
moveMapCamera(position)
return@setOnClickListener true
}
}
mapViewModel.addMarkerList(this)
}
}
}
Expand Down
Loading
Loading