From 288d9969bb74a610eede1a08051135173dede1d1 Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 01:58:50 +0900 Subject: [PATCH 01/17] =?UTF-8?q?[feat/#109]=20=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20request=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/voting/remote/dto/PlaceVoteRequest.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 app/src/main/java/com/moive/app/data/voting/remote/dto/PlaceVoteRequest.kt diff --git a/app/src/main/java/com/moive/app/data/voting/remote/dto/PlaceVoteRequest.kt b/app/src/main/java/com/moive/app/data/voting/remote/dto/PlaceVoteRequest.kt new file mode 100644 index 00000000..02547059 --- /dev/null +++ b/app/src/main/java/com/moive/app/data/voting/remote/dto/PlaceVoteRequest.kt @@ -0,0 +1,10 @@ +package com.moive.app.data.voting.remote.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PlaceVoteRequest( + @SerialName("recommendedPlaceIds") + val recommendedPlaceIds: List, +) From 6166a1ad9e46612eaaf13b5919f4a732511763c8 Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 01:59:14 +0900 Subject: [PATCH 02/17] =?UTF-8?q?[feat/#109]=20=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20service,=20datasource/impl=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../voting/remote/datasource/VotingRemoteDataSource.kt | 6 ++++++ .../remote/datasource/VotingRemoteDataSourceImpl.kt | 7 +++++++ .../app/data/voting/remote/service/VotingService.kt | 9 +++++++++ 3 files changed, 22 insertions(+) diff --git a/app/src/main/java/com/moive/app/data/voting/remote/datasource/VotingRemoteDataSource.kt b/app/src/main/java/com/moive/app/data/voting/remote/datasource/VotingRemoteDataSource.kt index fd49b9e2..d2470b87 100644 --- a/app/src/main/java/com/moive/app/data/voting/remote/datasource/VotingRemoteDataSource.kt +++ b/app/src/main/java/com/moive/app/data/voting/remote/datasource/VotingRemoteDataSource.kt @@ -1,6 +1,7 @@ package com.moive.app.data.voting.remote.datasource import com.moive.app.data.common.dto.BaseResponse +import com.moive.app.data.voting.remote.dto.PlaceVoteRequest import com.moive.app.data.voting.remote.dto.RecommendedAreaListResponse import com.moive.app.data.voting.remote.dto.RecommendedPlaceDetailResponse import com.moive.app.data.voting.remote.dto.RecommendedPlaceListResponse @@ -24,4 +25,9 @@ interface VotingRemoteDataSource { meetingId: Long, recommendedPlaceId: Long, ): BaseResponse + + suspend fun postPlaceVotes( + meetingId: Long, + request: PlaceVoteRequest, + ): BaseResponse } diff --git a/app/src/main/java/com/moive/app/data/voting/remote/datasource/VotingRemoteDataSourceImpl.kt b/app/src/main/java/com/moive/app/data/voting/remote/datasource/VotingRemoteDataSourceImpl.kt index 739292fc..57996f4e 100644 --- a/app/src/main/java/com/moive/app/data/voting/remote/datasource/VotingRemoteDataSourceImpl.kt +++ b/app/src/main/java/com/moive/app/data/voting/remote/datasource/VotingRemoteDataSourceImpl.kt @@ -1,6 +1,7 @@ package com.moive.app.data.voting.remote.datasource import com.moive.app.data.common.dto.BaseResponse +import com.moive.app.data.voting.remote.dto.PlaceVoteRequest import com.moive.app.data.voting.remote.dto.RecommendedAreaListResponse import com.moive.app.data.voting.remote.dto.RecommendedPlaceDetailResponse import com.moive.app.data.voting.remote.dto.RecommendedPlaceListResponse @@ -33,4 +34,10 @@ class VotingRemoteDataSourceImpl @Inject constructor( recommendedPlaceId: Long, ): BaseResponse = votingService.getRecommendedPlaceRoute(meetingId, recommendedPlaceId) + + override suspend fun postPlaceVotes( + meetingId: Long, + request: PlaceVoteRequest, + ): BaseResponse = + votingService.postPlaceVotes(meetingId, request) } diff --git a/app/src/main/java/com/moive/app/data/voting/remote/service/VotingService.kt b/app/src/main/java/com/moive/app/data/voting/remote/service/VotingService.kt index 39ec6a17..91e26664 100644 --- a/app/src/main/java/com/moive/app/data/voting/remote/service/VotingService.kt +++ b/app/src/main/java/com/moive/app/data/voting/remote/service/VotingService.kt @@ -1,11 +1,14 @@ package com.moive.app.data.voting.remote.service import com.moive.app.data.common.dto.BaseResponse +import com.moive.app.data.voting.remote.dto.PlaceVoteRequest import com.moive.app.data.voting.remote.dto.RecommendedAreaListResponse import com.moive.app.data.voting.remote.dto.RecommendedPlaceDetailResponse import com.moive.app.data.voting.remote.dto.RecommendedPlaceListResponse import com.moive.app.data.voting.remote.dto.RecommendedPlaceRouteResponse +import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.POST import retrofit2.http.Path interface VotingService { @@ -33,4 +36,10 @@ interface VotingService { @Path("meetingId") meetingId: Long, @Path("recommendedPlaceId") recommendedPlaceId: Long, ): BaseResponse + + @POST("meetings/{meetingId}/place-votes") + suspend fun postPlaceVotes( + @Path("meetingId") meetingId: Long, + @Body request: PlaceVoteRequest, + ): BaseResponse } From 1f7bd977306d5e2a0746b56bce1da9bae26f37fa Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 01:59:31 +0900 Subject: [PATCH 03/17] =?UTF-8?q?[feat/#109]=20=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20repository/impl=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/voting/repository/VotingRepository.kt | 5 +++++ .../data/voting/repository/VotingRepositoryImpl.kt | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/app/src/main/java/com/moive/app/data/voting/repository/VotingRepository.kt b/app/src/main/java/com/moive/app/data/voting/repository/VotingRepository.kt index fdc1dde6..8caec5ae 100644 --- a/app/src/main/java/com/moive/app/data/voting/repository/VotingRepository.kt +++ b/app/src/main/java/com/moive/app/data/voting/repository/VotingRepository.kt @@ -24,4 +24,9 @@ interface VotingRepository { recommendedPlaceId: Long, current: PlaceDetailModel, ): Result + + suspend fun postPlaceVotes( + meetingId: Long, + recommendedPlaceIds: List, + ): Result } diff --git a/app/src/main/java/com/moive/app/data/voting/repository/VotingRepositoryImpl.kt b/app/src/main/java/com/moive/app/data/voting/repository/VotingRepositoryImpl.kt index 4a41faeb..d6b60b45 100644 --- a/app/src/main/java/com/moive/app/data/voting/repository/VotingRepositoryImpl.kt +++ b/app/src/main/java/com/moive/app/data/voting/repository/VotingRepositoryImpl.kt @@ -2,12 +2,14 @@ package com.moive.app.data.voting.repository import com.moive.app.core.utils.suspendRunCatching import com.moive.app.data.common.dto.checkData +import com.moive.app.data.common.dto.checkSuccess import com.moive.app.data.voting.mapper.applyTo import com.moive.app.data.voting.mapper.toModel import com.moive.app.data.voting.model.PlaceDetailModel import com.moive.app.data.voting.model.PlaceRecommendedPlaceCardItemModel import com.moive.app.data.voting.model.RegionPinModel import com.moive.app.data.voting.remote.datasource.VotingRemoteDataSource +import com.moive.app.data.voting.remote.dto.PlaceVoteRequest import javax.inject.Inject class VotingRepositoryImpl @Inject constructor( @@ -55,4 +57,13 @@ class VotingRepositoryImpl @Inject constructor( .checkData() .applyTo(current) } + + override suspend fun postPlaceVotes( + meetingId: Long, + recommendedPlaceIds: List, + ): Result = + suspendRunCatching { + votingRemoteDataSource.postPlaceVotes(meetingId, PlaceVoteRequest(recommendedPlaceIds)) + .checkSuccess() + } } From 969993850aac117ac2984f7480553e437739112a Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:00:39 +0900 Subject: [PATCH 04/17] =?UTF-8?q?[feat/#109]=20=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20api=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/presentation/voting/VotingContract.kt | 14 ++++++++ .../app/presentation/voting/VotingScreen.kt | 18 +++++++++- .../presentation/voting/VotingViewModel.kt | 34 +++++++++++++++++++ .../voting/component/PlaceListContent.kt | 2 ++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/moive/app/presentation/voting/VotingContract.kt b/app/src/main/java/com/moive/app/presentation/voting/VotingContract.kt index 90c4d318..40c37c0b 100644 --- a/app/src/main/java/com/moive/app/presentation/voting/VotingContract.kt +++ b/app/src/main/java/com/moive/app/presentation/voting/VotingContract.kt @@ -19,6 +19,7 @@ interface VotingContract { val recommendedPlaceUiState: RecommendedPlaceUiState = RecommendedPlaceUiState.Idle, val recommendedPlaceDetailUiState: RecommendedPlaceDetailUiState = RecommendedPlaceDetailUiState.Idle, val recommendedPlaceRouteUiState: RecommendedPlaceRouteUiState = RecommendedPlaceRouteUiState.Idle, + val placeVoteUiState: PlaceVoteUiState = PlaceVoteUiState.Idle, val isPlaceListVisible: Boolean = false, val selectedRegionId: Long? = null, val selectedRegionName: String? = null, @@ -61,6 +62,10 @@ interface VotingContract { RECOMMENDATION, DETAIL; } + + sealed class SideEffect { + data object NavigateToVoteStatus : SideEffect() + } } sealed interface RecommendedAreaUiState { @@ -98,3 +103,12 @@ sealed interface RecommendedPlaceRouteUiState { val msg: String, ) : RecommendedPlaceRouteUiState } + +sealed interface PlaceVoteUiState { + data object Idle : PlaceVoteUiState + data object Loading : PlaceVoteUiState + data object Success : PlaceVoteUiState + data class Failure( + val msg: String, + ) : PlaceVoteUiState +} diff --git a/app/src/main/java/com/moive/app/presentation/voting/VotingScreen.kt b/app/src/main/java/com/moive/app/presentation/voting/VotingScreen.kt index 891d55fe..10923ca5 100644 --- a/app/src/main/java/com/moive/app/presentation/voting/VotingScreen.kt +++ b/app/src/main/java/com/moive/app/presentation/voting/VotingScreen.kt @@ -5,12 +5,16 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.repeatOnLifecycle import com.moive.app.core.designsystem.theme.MoiveTheme import com.moive.app.core.extensions.openUrl import com.moive.app.data.voting.model.RegionPinModel @@ -29,11 +33,22 @@ fun VotingRoute( ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current BackHandler(enabled = uiState.step != Step.RECOMMENDATION) { viewModel.backToPlaceList() } + LaunchedEffect(lifecycleOwner) { + lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.sideEffect.collect { sideEffect -> + when (sideEffect) { + VotingContract.SideEffect.NavigateToVoteStatus -> navigateToVoteStatus() + } + } + } + } + VotingScreen( innerPadding = innerPadding, uiState = uiState, @@ -52,7 +67,7 @@ fun VotingRoute( } }, onSelectButtonClick = viewModel::onSelectButtonClick, - onCompleteButtonClick = navigateToVoteStatus, + onCompleteButtonClick = viewModel::onCompleteButtonClick, modifier = modifier, ) } @@ -90,6 +105,7 @@ private fun VotingScreen( onResetClick = onResetClick, onBackClick = onBackClick, onCompleteButtonClick = onCompleteButtonClick, + isCompleteButtonLoading = uiState.placeVoteUiState is PlaceVoteUiState.Loading, ) if (uiState.step == Step.DETAIL) { diff --git a/app/src/main/java/com/moive/app/presentation/voting/VotingViewModel.kt b/app/src/main/java/com/moive/app/presentation/voting/VotingViewModel.kt index 53de08a6..86fcdc05 100644 --- a/app/src/main/java/com/moive/app/presentation/voting/VotingViewModel.kt +++ b/app/src/main/java/com/moive/app/presentation/voting/VotingViewModel.kt @@ -13,8 +13,10 @@ import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import timber.log.Timber @@ -31,8 +33,12 @@ class VotingViewModel @Inject constructor( private val _uiState = MutableStateFlow(VotingContract.State()) val uiState = _uiState.asStateFlow() + private val _sideEffect = Channel(Channel.BUFFERED) + val sideEffect = _sideEffect.receiveAsFlow() + private var recommendedPlacesJob: Job? = null private var placeDetailJob: Job? = null + private var placeVoteJob: Job? = null init { getRecommendedAreas() @@ -226,6 +232,33 @@ class VotingViewModel @Inject constructor( Timber.tag(TAG).e(KAKAO_MAP_ERROR) } + fun onCompleteButtonClick() { + if (placeVoteJob?.isActive == true) return + + val recommendedPlaceIds = _uiState.value.selectedPlaceList.toList() + if (recommendedPlaceIds.isEmpty()) return + + placeVoteJob = viewModelScope.launch { + _uiState.update { it.copy(placeVoteUiState = PlaceVoteUiState.Loading) } + + votingRepository.postPlaceVotes(meetingId, recommendedPlaceIds) + .onSuccess { + _uiState.update { it.copy(placeVoteUiState = PlaceVoteUiState.Success) } + _sideEffect.send(VotingContract.SideEffect.NavigateToVoteStatus) + } + .onFailure { error -> + Timber.tag(TAG).e(error, PLACE_VOTE_FAILURE_MESSAGE) + _uiState.update { + it.copy( + placeVoteUiState = PlaceVoteUiState.Failure( + error.message ?: UNKNOWN_ERROR_MESSAGE, + ), + ) + } + } + } + } + companion object { private const val TAG = "Voting" private const val KAKAO_MAP_ERROR = "카카오 맵을 열 수 없습니다." @@ -233,6 +266,7 @@ class VotingViewModel @Inject constructor( private const val RECOMMENDED_PLACE_FAILURE_MESSAGE = "추천 장소 조회에 실패했습니다." private const val RECOMMENDED_PLACE_DETAIL_FAILURE_MESSAGE = "추천 장소 상세 조회에 실패했습니다." private const val RECOMMENDED_PLACE_ROUTE_FAILURE_MESSAGE = "이동 경로 조회에 실패했습니다." + private const val PLACE_VOTE_FAILURE_MESSAGE = "장소 투표에 실패했습니다." private const val UNKNOWN_ERROR_MESSAGE = "알 수 없는 에러가 발생했습니다." } } diff --git a/app/src/main/java/com/moive/app/presentation/voting/component/PlaceListContent.kt b/app/src/main/java/com/moive/app/presentation/voting/component/PlaceListContent.kt index 36e615e4..e9288a40 100644 --- a/app/src/main/java/com/moive/app/presentation/voting/component/PlaceListContent.kt +++ b/app/src/main/java/com/moive/app/presentation/voting/component/PlaceListContent.kt @@ -66,6 +66,7 @@ fun PlaceListContent( onBackClick: () -> Unit, onCompleteButtonClick: () -> Unit, modifier: Modifier = Modifier, + isCompleteButtonLoading: Boolean = false, ) { var kakaoMapState by remember { mutableStateOf(null) } var regionPinEntries by remember { mutableStateOf>>(emptyList()) } @@ -176,6 +177,7 @@ fun PlaceListContent( size = MoiveButtonSize.LARGE, onClick = onCompleteButtonClick, enabled = selectedPlaceIds.isNotEmpty(), + isLoading = isCompleteButtonLoading, modifier = Modifier.weight(1f), ) } From f0d68bff40589e983c76426ad3f67380188b9c64 Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:02:35 +0900 Subject: [PATCH 05/17] =?UTF-8?q?[del/#109]=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/moive/app/data/votingstatus/di/.gitkeep | 0 app/src/main/java/com/moive/app/data/votingstatus/mapper/.gitkeep | 0 .../com/moive/app/data/votingstatus/remote/datasource/.gitkeep | 0 .../main/java/com/moive/app/data/votingstatus/remote/dto/.gitkeep | 0 .../java/com/moive/app/data/votingstatus/remote/service/.gitkeep | 0 .../main/java/com/moive/app/data/votingstatus/repository/.gitkeep | 0 6 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/src/main/java/com/moive/app/data/votingstatus/di/.gitkeep delete mode 100644 app/src/main/java/com/moive/app/data/votingstatus/mapper/.gitkeep delete mode 100644 app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/.gitkeep delete mode 100644 app/src/main/java/com/moive/app/data/votingstatus/remote/dto/.gitkeep delete mode 100644 app/src/main/java/com/moive/app/data/votingstatus/remote/service/.gitkeep delete mode 100644 app/src/main/java/com/moive/app/data/votingstatus/repository/.gitkeep diff --git a/app/src/main/java/com/moive/app/data/votingstatus/di/.gitkeep b/app/src/main/java/com/moive/app/data/votingstatus/di/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/src/main/java/com/moive/app/data/votingstatus/mapper/.gitkeep b/app/src/main/java/com/moive/app/data/votingstatus/mapper/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/.gitkeep b/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/src/main/java/com/moive/app/data/votingstatus/remote/dto/.gitkeep b/app/src/main/java/com/moive/app/data/votingstatus/remote/dto/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/src/main/java/com/moive/app/data/votingstatus/remote/service/.gitkeep b/app/src/main/java/com/moive/app/data/votingstatus/remote/service/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/src/main/java/com/moive/app/data/votingstatus/repository/.gitkeep b/app/src/main/java/com/moive/app/data/votingstatus/repository/.gitkeep deleted file mode 100644 index e69de29b..00000000 From 8aeb58f3c1e15ba3d0b3eca320dd40e03b73b330 Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:11:53 +0900 Subject: [PATCH 06/17] =?UTF-8?q?[feat/#109]=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20=ED=98=84=ED=99=A9=20response,=20model,=20?= =?UTF-8?q?mapper=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../votingstatus/mapper/VoteStatusMapper.kt | 21 +++++++++++++++ .../model/ScheduleVoteCandidateModel.kt | 8 ------ .../model/ScheduleVoteResultModel.kt | 18 +++++++++++++ .../remote/dto/ScheduleVoteResultResponse.kt | 26 +++++++++++++++++++ 4 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/com/moive/app/data/votingstatus/mapper/VoteStatusMapper.kt delete mode 100644 app/src/main/java/com/moive/app/data/votingstatus/model/ScheduleVoteCandidateModel.kt create mode 100644 app/src/main/java/com/moive/app/data/votingstatus/model/ScheduleVoteResultModel.kt create mode 100644 app/src/main/java/com/moive/app/data/votingstatus/remote/dto/ScheduleVoteResultResponse.kt diff --git a/app/src/main/java/com/moive/app/data/votingstatus/mapper/VoteStatusMapper.kt b/app/src/main/java/com/moive/app/data/votingstatus/mapper/VoteStatusMapper.kt new file mode 100644 index 00000000..1c8817f7 --- /dev/null +++ b/app/src/main/java/com/moive/app/data/votingstatus/mapper/VoteStatusMapper.kt @@ -0,0 +1,21 @@ +package com.moive.app.data.votingstatus.mapper + +import com.moive.app.data.votingstatus.model.ScheduleVoteCandidateModel +import com.moive.app.data.votingstatus.model.ScheduleVoteResultModel +import com.moive.app.data.votingstatus.remote.dto.ScheduleVoteCandidateItem +import com.moive.app.data.votingstatus.remote.dto.ScheduleVoteResultResponse + +fun ScheduleVoteResultResponse.toModel(): ScheduleVoteResultModel = + ScheduleVoteResultModel( + isVoteSkipped = isVoteSkipped, + totalVoterCount = totalVoterCnt, + candidates = candidates.map { it.toModel() }, + ) + +fun ScheduleVoteCandidateItem.toModel(): ScheduleVoteCandidateModel = + ScheduleVoteCandidateModel( + meetingDate = meetingDate, + meetingTime = meetingTime, + voterCount = voterCnt, + isVotedByMe = isVotedByMe, + ) diff --git a/app/src/main/java/com/moive/app/data/votingstatus/model/ScheduleVoteCandidateModel.kt b/app/src/main/java/com/moive/app/data/votingstatus/model/ScheduleVoteCandidateModel.kt deleted file mode 100644 index de056627..00000000 --- a/app/src/main/java/com/moive/app/data/votingstatus/model/ScheduleVoteCandidateModel.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.moive.app.data.votingstatus.model - -data class ScheduleVoteCandidateModel( - val meetingDate: String, - val meetingTime: String, - val voterCount: Int?, - val isVotedByMe: Boolean, -) diff --git a/app/src/main/java/com/moive/app/data/votingstatus/model/ScheduleVoteResultModel.kt b/app/src/main/java/com/moive/app/data/votingstatus/model/ScheduleVoteResultModel.kt new file mode 100644 index 00000000..eb25704b --- /dev/null +++ b/app/src/main/java/com/moive/app/data/votingstatus/model/ScheduleVoteResultModel.kt @@ -0,0 +1,18 @@ +package com.moive.app.data.votingstatus.model + +import androidx.compose.runtime.Immutable + +@Immutable +data class ScheduleVoteResultModel( + val isVoteSkipped: Boolean, + val totalVoterCount: Int?, + val candidates: List, +) + +@Immutable +data class ScheduleVoteCandidateModel( + val meetingDate: String, + val meetingTime: String, + val voterCount: Int?, + val isVotedByMe: Boolean, +) diff --git a/app/src/main/java/com/moive/app/data/votingstatus/remote/dto/ScheduleVoteResultResponse.kt b/app/src/main/java/com/moive/app/data/votingstatus/remote/dto/ScheduleVoteResultResponse.kt new file mode 100644 index 00000000..3cb50581 --- /dev/null +++ b/app/src/main/java/com/moive/app/data/votingstatus/remote/dto/ScheduleVoteResultResponse.kt @@ -0,0 +1,26 @@ +package com.moive.app.data.votingstatus.remote.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ScheduleVoteResultResponse( + @SerialName("isVoteSkipped") + val isVoteSkipped: Boolean, + @SerialName("totalVoterCnt") + val totalVoterCnt: Int?, + @SerialName("candidates") + val candidates: List, +) + +@Serializable +data class ScheduleVoteCandidateItem( + @SerialName("meetingDate") + val meetingDate: String, + @SerialName("meetingTime") + val meetingTime: String, + @SerialName("voterCnt") + val voterCnt: Int?, + @SerialName("isVotedByMe") + val isVotedByMe: Boolean, +) From c304f6beade5a21707b453aaa123009b4f951375 Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:12:19 +0900 Subject: [PATCH 07/17] =?UTF-8?q?[feat/#109]=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20=ED=98=84=ED=99=A9=20service,=20datasource?= =?UTF-8?q?/impl=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datasource/VoteStatusRemoteDataSource.kt | 8 ++++++++ .../datasource/VoteStatusRemoteDataSourceImpl.kt | 14 ++++++++++++++ .../remote/service/VoteStatusService.kt | 13 +++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSource.kt create mode 100644 app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSourceImpl.kt create mode 100644 app/src/main/java/com/moive/app/data/votingstatus/remote/service/VoteStatusService.kt diff --git a/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSource.kt b/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSource.kt new file mode 100644 index 00000000..9b2724eb --- /dev/null +++ b/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSource.kt @@ -0,0 +1,8 @@ +package com.moive.app.data.votingstatus.remote.datasource + +import com.moive.app.data.common.dto.BaseResponse +import com.moive.app.data.votingstatus.remote.dto.ScheduleVoteResultResponse + +interface VoteStatusRemoteDataSource { + suspend fun getScheduleVoteResult(meetingId: Long): BaseResponse +} diff --git a/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSourceImpl.kt b/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSourceImpl.kt new file mode 100644 index 00000000..be100bec --- /dev/null +++ b/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSourceImpl.kt @@ -0,0 +1,14 @@ +package com.moive.app.data.votingstatus.remote.datasource + +import com.moive.app.data.common.dto.BaseResponse +import com.moive.app.data.votingstatus.remote.dto.ScheduleVoteResultResponse +import com.moive.app.data.votingstatus.remote.service.VoteStatusService +import javax.inject.Inject + +class VoteStatusRemoteDataSourceImpl @Inject constructor( + private val voteStatusService: VoteStatusService, +) : VoteStatusRemoteDataSource { + + override suspend fun getScheduleVoteResult(meetingId: Long): BaseResponse = + voteStatusService.getScheduleVoteResult(meetingId) +} diff --git a/app/src/main/java/com/moive/app/data/votingstatus/remote/service/VoteStatusService.kt b/app/src/main/java/com/moive/app/data/votingstatus/remote/service/VoteStatusService.kt new file mode 100644 index 00000000..eaed77cc --- /dev/null +++ b/app/src/main/java/com/moive/app/data/votingstatus/remote/service/VoteStatusService.kt @@ -0,0 +1,13 @@ +package com.moive.app.data.votingstatus.remote.service + +import com.moive.app.data.common.dto.BaseResponse +import com.moive.app.data.votingstatus.remote.dto.ScheduleVoteResultResponse +import retrofit2.http.GET +import retrofit2.http.Path + +interface VoteStatusService { + @GET("meetings/{meetingId}/date-votes/result") + suspend fun getScheduleVoteResult( + @Path("meetingId") meetingId: Long, + ): BaseResponse +} From 08e4ec70d24e6ab266e33c4900b07672405a0aa4 Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:12:37 +0900 Subject: [PATCH 08/17] =?UTF-8?q?[feat/#109]=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20=ED=98=84=ED=99=A9=20repository/impl=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/VoteStatusRepository.kt | 7 +++++++ .../repository/VoteStatusRepositoryImpl.kt | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepository.kt create mode 100644 app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepositoryImpl.kt diff --git a/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepository.kt b/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepository.kt new file mode 100644 index 00000000..ca0665ee --- /dev/null +++ b/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepository.kt @@ -0,0 +1,7 @@ +package com.moive.app.data.votingstatus.repository + +import com.moive.app.data.votingstatus.model.ScheduleVoteResultModel + +interface VoteStatusRepository { + suspend fun getScheduleVoteResult(meetingId: Long): Result +} diff --git a/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepositoryImpl.kt b/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepositoryImpl.kt new file mode 100644 index 00000000..44e31479 --- /dev/null +++ b/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepositoryImpl.kt @@ -0,0 +1,20 @@ +package com.moive.app.data.votingstatus.repository + +import com.moive.app.core.utils.suspendRunCatching +import com.moive.app.data.common.dto.checkData +import com.moive.app.data.votingstatus.mapper.toModel +import com.moive.app.data.votingstatus.model.ScheduleVoteResultModel +import com.moive.app.data.votingstatus.remote.datasource.VoteStatusRemoteDataSource +import javax.inject.Inject + +class VoteStatusRepositoryImpl @Inject constructor( + private val voteStatusRemoteDataSource: VoteStatusRemoteDataSource, +) : VoteStatusRepository { + + override suspend fun getScheduleVoteResult(meetingId: Long): Result = + suspendRunCatching { + voteStatusRemoteDataSource.getScheduleVoteResult(meetingId) + .checkData() + .toModel() + } +} From 04f31861f6779951775a7ae9152850c03050a78e Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:13:09 +0900 Subject: [PATCH 09/17] =?UTF-8?q?[feat/#109]=20VoteStatus=20DI=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../di/VoteStatusDataSourceModule.kt | 20 +++++++++++++++++ .../di/VoteStatusRepositoryModule.kt | 20 +++++++++++++++++ .../di/VoteStatusServiceModule.kt | 22 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 app/src/main/java/com/moive/app/data/votingstatus/di/VoteStatusDataSourceModule.kt create mode 100644 app/src/main/java/com/moive/app/data/votingstatus/di/VoteStatusRepositoryModule.kt create mode 100644 app/src/main/java/com/moive/app/data/votingstatus/di/VoteStatusServiceModule.kt diff --git a/app/src/main/java/com/moive/app/data/votingstatus/di/VoteStatusDataSourceModule.kt b/app/src/main/java/com/moive/app/data/votingstatus/di/VoteStatusDataSourceModule.kt new file mode 100644 index 00000000..6ac28c8d --- /dev/null +++ b/app/src/main/java/com/moive/app/data/votingstatus/di/VoteStatusDataSourceModule.kt @@ -0,0 +1,20 @@ +package com.moive.app.data.votingstatus.di + +import com.moive.app.data.votingstatus.remote.datasource.VoteStatusRemoteDataSource +import com.moive.app.data.votingstatus.remote.datasource.VoteStatusRemoteDataSourceImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class VoteStatusDataSourceModule { + + @Binds + @Singleton + abstract fun bindVoteStatusRemoteDataSource( + voteStatusRemoteDataSourceImpl: VoteStatusRemoteDataSourceImpl, + ): VoteStatusRemoteDataSource +} diff --git a/app/src/main/java/com/moive/app/data/votingstatus/di/VoteStatusRepositoryModule.kt b/app/src/main/java/com/moive/app/data/votingstatus/di/VoteStatusRepositoryModule.kt new file mode 100644 index 00000000..62054bf4 --- /dev/null +++ b/app/src/main/java/com/moive/app/data/votingstatus/di/VoteStatusRepositoryModule.kt @@ -0,0 +1,20 @@ +package com.moive.app.data.votingstatus.di + +import com.moive.app.data.votingstatus.repository.VoteStatusRepository +import com.moive.app.data.votingstatus.repository.VoteStatusRepositoryImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class VoteStatusRepositoryModule { + + @Binds + @Singleton + abstract fun bindVoteStatusRepository( + voteStatusRepositoryImpl: VoteStatusRepositoryImpl, + ): VoteStatusRepository +} diff --git a/app/src/main/java/com/moive/app/data/votingstatus/di/VoteStatusServiceModule.kt b/app/src/main/java/com/moive/app/data/votingstatus/di/VoteStatusServiceModule.kt new file mode 100644 index 00000000..90649a7e --- /dev/null +++ b/app/src/main/java/com/moive/app/data/votingstatus/di/VoteStatusServiceModule.kt @@ -0,0 +1,22 @@ +package com.moive.app.data.votingstatus.di + +import com.moive.app.core.network.di.Auth +import com.moive.app.data.votingstatus.remote.service.VoteStatusService +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import retrofit2.Retrofit +import retrofit2.create +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object VoteStatusServiceModule { + + @Provides + @Singleton + fun provideVoteStatusService( + @Auth retrofit: Retrofit, + ): VoteStatusService = retrofit.create() +} From 1e14b738cf7dd6d0e5cc4fb5c71568ebab98793d Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:16:55 +0900 Subject: [PATCH 10/17] =?UTF-8?q?[feat/#109]=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20=ED=98=84=ED=99=A9=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../votestatus/VoteStatusContract.kt | 10 ++++ .../votestatus/VoteStatusViewModel.kt | 51 ++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusContract.kt b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusContract.kt index 220dcc1e..9acea366 100644 --- a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusContract.kt +++ b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusContract.kt @@ -13,6 +13,7 @@ interface VoteStatusContract { @Immutable data class State( val step: Step = Step.LIST, + val scheduleVoteResultUiState: ScheduleVoteResultUiState = ScheduleVoteResultUiState.Idle, val isScheduleVoteSkipped: Boolean = false, val scheduleTotalVoterCount: Int? = 5, val scheduleCandidates: ImmutableList = persistentListOf( @@ -78,3 +79,12 @@ interface VoteStatusContract { DETAIL; } } + +sealed interface ScheduleVoteResultUiState { + data object Idle : ScheduleVoteResultUiState + data object Loading : ScheduleVoteResultUiState + data object Success : ScheduleVoteResultUiState + data class Failure( + val msg: String, + ) : ScheduleVoteResultUiState +} diff --git a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt index 36401e30..bf783a75 100644 --- a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt +++ b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt @@ -1,22 +1,62 @@ package com.moive.app.presentation.votestatus +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import com.moive.app.data.votingstatus.repository.VoteStatusRepository import com.moive.app.presentation.votestatus.VoteStatusContract.Step +import com.moive.app.presentation.votestatus.navigation.VoteStatus import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @HiltViewModel class VoteStatusViewModel @Inject constructor( - + savedStateHandle: SavedStateHandle, + private val voteStatusRepository: VoteStatusRepository, ) : ViewModel() { + private val meetingId: Long = savedStateHandle.toRoute().meetingId + private val _uiState = MutableStateFlow(VoteStatusContract.State()) val uiState = _uiState.asStateFlow() + init { + getScheduleVoteResult() + } + + private fun getScheduleVoteResult() = viewModelScope.launch { + _uiState.update { it.copy(scheduleVoteResultUiState = ScheduleVoteResultUiState.Loading) } + + voteStatusRepository.getScheduleVoteResult(meetingId) + .onSuccess { result -> + _uiState.update { + it.copy( + scheduleVoteResultUiState = ScheduleVoteResultUiState.Success, + isScheduleVoteSkipped = result.isVoteSkipped, + scheduleTotalVoterCount = result.totalVoterCount, + scheduleCandidates = result.candidates.toImmutableList(), + ) + } + } + .onFailure { error -> + Timber.tag(TAG).e(error, SCHEDULE_VOTE_RESULT_FAILURE_MESSAGE) + _uiState.update { + it.copy( + scheduleVoteResultUiState = ScheduleVoteResultUiState.Failure( + error.message ?: UNKNOWN_ERROR_MESSAGE, + ), + ) + } + } + } + fun onPlaceItemClick(placeId: Long) { _uiState.update { it.copy(step = Step.DETAIL, currentPlaceId = placeId) } } @@ -27,6 +67,13 @@ class VoteStatusViewModel @Inject constructor( fun onKakaoMapRouteOpened(opened: Boolean) { if (opened) return - Timber.tag("VoteStatus").e("카카오 맵을 열 수 없습니다.") + Timber.tag(TAG).e(KAKAO_MAP_ERROR) + } + + companion object { + private const val TAG = "VoteStatus" + private const val KAKAO_MAP_ERROR = "카카오 맵을 열 수 없습니다." + private const val SCHEDULE_VOTE_RESULT_FAILURE_MESSAGE = "일정 투표 현황 조회에 실패했습니다." + private const val UNKNOWN_ERROR_MESSAGE = "알 수 없는 에러가 발생했습니다." } } From 8c7e8fe91b03ae7bf07f08ee1e9e0828af69ce5d Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:17:35 +0900 Subject: [PATCH 11/17] =?UTF-8?q?[feat/#109]=20voting=20->=20votestatus=20?= =?UTF-8?q?meetingId=20SavedStateHandle=EB=A1=9C=20=EC=A0=84=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../votestatus/navigation/VoteStatusNavigation.kt | 7 +++++-- .../com/moive/app/presentation/voting/VotingContract.kt | 2 +- .../java/com/moive/app/presentation/voting/VotingScreen.kt | 4 ++-- .../com/moive/app/presentation/voting/VotingViewModel.kt | 2 +- .../app/presentation/voting/navigation/VotingNavigation.kt | 7 +++++-- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/moive/app/presentation/votestatus/navigation/VoteStatusNavigation.kt b/app/src/main/java/com/moive/app/presentation/votestatus/navigation/VoteStatusNavigation.kt index fa4e313a..ba23c7a2 100644 --- a/app/src/main/java/com/moive/app/presentation/votestatus/navigation/VoteStatusNavigation.kt +++ b/app/src/main/java/com/moive/app/presentation/votestatus/navigation/VoteStatusNavigation.kt @@ -11,8 +11,9 @@ import com.moive.app.presentation.votestatus.VoteStatusRoute import kotlinx.serialization.Serializable fun NavController.navigateToVoteStatus( + meetingId: Long, navOptions: NavOptions? = null -) = navigate(VoteStatus, navOptions) +) = navigate(VoteStatus(meetingId), navOptions) fun NavGraphBuilder.voteStatusGraph( navController: NavController, @@ -27,4 +28,6 @@ fun NavGraphBuilder.voteStatusGraph( } @Serializable -data object VoteStatus : Route +data class VoteStatus( + val meetingId: Long +) : Route diff --git a/app/src/main/java/com/moive/app/presentation/voting/VotingContract.kt b/app/src/main/java/com/moive/app/presentation/voting/VotingContract.kt index 40c37c0b..3ab79d4e 100644 --- a/app/src/main/java/com/moive/app/presentation/voting/VotingContract.kt +++ b/app/src/main/java/com/moive/app/presentation/voting/VotingContract.kt @@ -64,7 +64,7 @@ interface VotingContract { } sealed class SideEffect { - data object NavigateToVoteStatus : SideEffect() + data class NavigateToVoteStatus(val meetingId: Long) : SideEffect() } } diff --git a/app/src/main/java/com/moive/app/presentation/voting/VotingScreen.kt b/app/src/main/java/com/moive/app/presentation/voting/VotingScreen.kt index 10923ca5..1372edfe 100644 --- a/app/src/main/java/com/moive/app/presentation/voting/VotingScreen.kt +++ b/app/src/main/java/com/moive/app/presentation/voting/VotingScreen.kt @@ -27,7 +27,7 @@ import kotlinx.collections.immutable.persistentSetOf fun VotingRoute( innerPadding: PaddingValues, navigateBack: () -> Unit, - navigateToVoteStatus: () -> Unit, + navigateToVoteStatus: (Long) -> Unit, modifier: Modifier = Modifier, viewModel: VotingViewModel = hiltViewModel(), ) { @@ -43,7 +43,7 @@ fun VotingRoute( lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.sideEffect.collect { sideEffect -> when (sideEffect) { - VotingContract.SideEffect.NavigateToVoteStatus -> navigateToVoteStatus() + is VotingContract.SideEffect.NavigateToVoteStatus -> navigateToVoteStatus(sideEffect.meetingId) } } } diff --git a/app/src/main/java/com/moive/app/presentation/voting/VotingViewModel.kt b/app/src/main/java/com/moive/app/presentation/voting/VotingViewModel.kt index 86fcdc05..d7c8587f 100644 --- a/app/src/main/java/com/moive/app/presentation/voting/VotingViewModel.kt +++ b/app/src/main/java/com/moive/app/presentation/voting/VotingViewModel.kt @@ -244,7 +244,7 @@ class VotingViewModel @Inject constructor( votingRepository.postPlaceVotes(meetingId, recommendedPlaceIds) .onSuccess { _uiState.update { it.copy(placeVoteUiState = PlaceVoteUiState.Success) } - _sideEffect.send(VotingContract.SideEffect.NavigateToVoteStatus) + _sideEffect.send(VotingContract.SideEffect.NavigateToVoteStatus(meetingId)) } .onFailure { error -> Timber.tag(TAG).e(error, PLACE_VOTE_FAILURE_MESSAGE) diff --git a/app/src/main/java/com/moive/app/presentation/voting/navigation/VotingNavigation.kt b/app/src/main/java/com/moive/app/presentation/voting/navigation/VotingNavigation.kt index 087db98e..fc2fb975 100644 --- a/app/src/main/java/com/moive/app/presentation/voting/navigation/VotingNavigation.kt +++ b/app/src/main/java/com/moive/app/presentation/voting/navigation/VotingNavigation.kt @@ -25,8 +25,9 @@ fun NavGraphBuilder.votingGraph( composable { VotingRoute( navigateBack = navController.safePopBackStack(), - navigateToVoteStatus = { + navigateToVoteStatus = { meetingId -> navController.navigateToVoteStatus( + meetingId = meetingId, navOptions = navOptions { popUpTo { inclusive = false @@ -40,4 +41,6 @@ fun NavGraphBuilder.votingGraph( } @Serializable -data class Voting(val meetingId: Long) : Route +data class Voting( + val meetingId: Long +) : Route From 3d8a55091bb70a17d5e9a972886677392d70a079 Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:25:35 +0900 Subject: [PATCH 12/17] =?UTF-8?q?[feat/#109]=20=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20=ED=98=84=ED=99=A9=20response,=20model,=20?= =?UTF-8?q?mapper=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../votingstatus/mapper/VoteStatusMapper.kt | 19 ++++++++++++++ .../model/PlaceVoteCandidateModel.kt | 8 ------ .../model/PlaceVoteResultModel.kt | 14 ++++++++++ .../remote/dto/PlaceVoteResultResponse.kt | 26 +++++++++++++++++++ 4 files changed, 59 insertions(+), 8 deletions(-) delete mode 100644 app/src/main/java/com/moive/app/data/votingstatus/model/PlaceVoteCandidateModel.kt create mode 100644 app/src/main/java/com/moive/app/data/votingstatus/model/PlaceVoteResultModel.kt create mode 100644 app/src/main/java/com/moive/app/data/votingstatus/remote/dto/PlaceVoteResultResponse.kt diff --git a/app/src/main/java/com/moive/app/data/votingstatus/mapper/VoteStatusMapper.kt b/app/src/main/java/com/moive/app/data/votingstatus/mapper/VoteStatusMapper.kt index 1c8817f7..1175030e 100644 --- a/app/src/main/java/com/moive/app/data/votingstatus/mapper/VoteStatusMapper.kt +++ b/app/src/main/java/com/moive/app/data/votingstatus/mapper/VoteStatusMapper.kt @@ -1,7 +1,11 @@ package com.moive.app.data.votingstatus.mapper +import com.moive.app.data.votingstatus.model.PlaceVoteCandidateModel +import com.moive.app.data.votingstatus.model.PlaceVoteResultModel import com.moive.app.data.votingstatus.model.ScheduleVoteCandidateModel import com.moive.app.data.votingstatus.model.ScheduleVoteResultModel +import com.moive.app.data.votingstatus.remote.dto.PlaceVoteCandidateItem +import com.moive.app.data.votingstatus.remote.dto.PlaceVoteResultResponse import com.moive.app.data.votingstatus.remote.dto.ScheduleVoteCandidateItem import com.moive.app.data.votingstatus.remote.dto.ScheduleVoteResultResponse @@ -19,3 +23,18 @@ fun ScheduleVoteCandidateItem.toModel(): ScheduleVoteCandidateModel = voterCount = voterCnt, isVotedByMe = isVotedByMe, ) + +fun PlaceVoteResultResponse.toModel(): PlaceVoteResultModel = + PlaceVoteResultModel( + isFinished = isFinished, + totalVoterCount = totalVoterCnt, + candidates = candidates.map { it.toModel() }, + ) + +fun PlaceVoteCandidateItem.toModel(): PlaceVoteCandidateModel = + PlaceVoteCandidateModel( + id = placeId, + placeName = placeName, + voterCount = voterCnt, + isVotedByMe = isVotedByMe, + ) diff --git a/app/src/main/java/com/moive/app/data/votingstatus/model/PlaceVoteCandidateModel.kt b/app/src/main/java/com/moive/app/data/votingstatus/model/PlaceVoteCandidateModel.kt deleted file mode 100644 index 690cbfc6..00000000 --- a/app/src/main/java/com/moive/app/data/votingstatus/model/PlaceVoteCandidateModel.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.moive.app.data.votingstatus.model - -data class PlaceVoteCandidateModel( - val id: Long, - val placeName: String, - val voterCount: Int, - val isVotedByMe: Boolean, -) diff --git a/app/src/main/java/com/moive/app/data/votingstatus/model/PlaceVoteResultModel.kt b/app/src/main/java/com/moive/app/data/votingstatus/model/PlaceVoteResultModel.kt new file mode 100644 index 00000000..727e31b2 --- /dev/null +++ b/app/src/main/java/com/moive/app/data/votingstatus/model/PlaceVoteResultModel.kt @@ -0,0 +1,14 @@ +package com.moive.app.data.votingstatus.model + +data class PlaceVoteResultModel( + val isFinished: Boolean, + val totalVoterCount: Int, + val candidates: List, +) + +data class PlaceVoteCandidateModel( + val id: Long, + val placeName: String?, + val voterCount: Int, + val isVotedByMe: Boolean, +) diff --git a/app/src/main/java/com/moive/app/data/votingstatus/remote/dto/PlaceVoteResultResponse.kt b/app/src/main/java/com/moive/app/data/votingstatus/remote/dto/PlaceVoteResultResponse.kt new file mode 100644 index 00000000..4654e220 --- /dev/null +++ b/app/src/main/java/com/moive/app/data/votingstatus/remote/dto/PlaceVoteResultResponse.kt @@ -0,0 +1,26 @@ +package com.moive.app.data.votingstatus.remote.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PlaceVoteResultResponse( + @SerialName("isFinished") + val isFinished: Boolean, + @SerialName("totalVoterCnt") + val totalVoterCnt: Int, + @SerialName("candidates") + val candidates: List, +) + +@Serializable +data class PlaceVoteCandidateItem( + @SerialName("placeId") + val placeId: Long, + @SerialName("placeName") + val placeName: String?, + @SerialName("voterCnt") + val voterCnt: Int, + @SerialName("isVotedByMe") + val isVotedByMe: Boolean, +) From dbaffd77d8d0c85757455e7a9a70ca476ebf5ef4 Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:25:59 +0900 Subject: [PATCH 13/17] =?UTF-8?q?[feat/#109]=20=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20=ED=98=84=ED=99=A9=20service,=20datasource?= =?UTF-8?q?/impl=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../remote/datasource/VoteStatusRemoteDataSource.kt | 3 +++ .../remote/datasource/VoteStatusRemoteDataSourceImpl.kt | 4 ++++ .../data/votingstatus/remote/service/VoteStatusService.kt | 6 ++++++ 3 files changed, 13 insertions(+) diff --git a/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSource.kt b/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSource.kt index 9b2724eb..bc650c57 100644 --- a/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSource.kt +++ b/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSource.kt @@ -1,8 +1,11 @@ package com.moive.app.data.votingstatus.remote.datasource import com.moive.app.data.common.dto.BaseResponse +import com.moive.app.data.votingstatus.remote.dto.PlaceVoteResultResponse import com.moive.app.data.votingstatus.remote.dto.ScheduleVoteResultResponse interface VoteStatusRemoteDataSource { suspend fun getScheduleVoteResult(meetingId: Long): BaseResponse + + suspend fun getPlaceVoteResult(meetingId: Long): BaseResponse } diff --git a/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSourceImpl.kt b/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSourceImpl.kt index be100bec..097de56b 100644 --- a/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSourceImpl.kt +++ b/app/src/main/java/com/moive/app/data/votingstatus/remote/datasource/VoteStatusRemoteDataSourceImpl.kt @@ -1,6 +1,7 @@ package com.moive.app.data.votingstatus.remote.datasource import com.moive.app.data.common.dto.BaseResponse +import com.moive.app.data.votingstatus.remote.dto.PlaceVoteResultResponse import com.moive.app.data.votingstatus.remote.dto.ScheduleVoteResultResponse import com.moive.app.data.votingstatus.remote.service.VoteStatusService import javax.inject.Inject @@ -11,4 +12,7 @@ class VoteStatusRemoteDataSourceImpl @Inject constructor( override suspend fun getScheduleVoteResult(meetingId: Long): BaseResponse = voteStatusService.getScheduleVoteResult(meetingId) + + override suspend fun getPlaceVoteResult(meetingId: Long): BaseResponse = + voteStatusService.getPlaceVoteResult(meetingId) } diff --git a/app/src/main/java/com/moive/app/data/votingstatus/remote/service/VoteStatusService.kt b/app/src/main/java/com/moive/app/data/votingstatus/remote/service/VoteStatusService.kt index eaed77cc..8b782b9d 100644 --- a/app/src/main/java/com/moive/app/data/votingstatus/remote/service/VoteStatusService.kt +++ b/app/src/main/java/com/moive/app/data/votingstatus/remote/service/VoteStatusService.kt @@ -1,6 +1,7 @@ package com.moive.app.data.votingstatus.remote.service import com.moive.app.data.common.dto.BaseResponse +import com.moive.app.data.votingstatus.remote.dto.PlaceVoteResultResponse import com.moive.app.data.votingstatus.remote.dto.ScheduleVoteResultResponse import retrofit2.http.GET import retrofit2.http.Path @@ -10,4 +11,9 @@ interface VoteStatusService { suspend fun getScheduleVoteResult( @Path("meetingId") meetingId: Long, ): BaseResponse + + @GET("meetings/{meetingId}/place-votes/result") + suspend fun getPlaceVoteResult( + @Path("meetingId") meetingId: Long, + ): BaseResponse } From 6970b95a5b96fc7d1e083068b16dd4069acd1cda Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:26:17 +0900 Subject: [PATCH 14/17] =?UTF-8?q?[feat/#109]=20=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20=ED=98=84=ED=99=A9=20repository/impl=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/votingstatus/repository/VoteStatusRepository.kt | 3 +++ .../votingstatus/repository/VoteStatusRepositoryImpl.kt | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepository.kt b/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepository.kt index ca0665ee..c332e031 100644 --- a/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepository.kt +++ b/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepository.kt @@ -1,7 +1,10 @@ package com.moive.app.data.votingstatus.repository +import com.moive.app.data.votingstatus.model.PlaceVoteResultModel import com.moive.app.data.votingstatus.model.ScheduleVoteResultModel interface VoteStatusRepository { suspend fun getScheduleVoteResult(meetingId: Long): Result + + suspend fun getPlaceVoteResult(meetingId: Long): Result } diff --git a/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepositoryImpl.kt b/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepositoryImpl.kt index 44e31479..89af4a69 100644 --- a/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepositoryImpl.kt +++ b/app/src/main/java/com/moive/app/data/votingstatus/repository/VoteStatusRepositoryImpl.kt @@ -3,6 +3,7 @@ package com.moive.app.data.votingstatus.repository import com.moive.app.core.utils.suspendRunCatching import com.moive.app.data.common.dto.checkData import com.moive.app.data.votingstatus.mapper.toModel +import com.moive.app.data.votingstatus.model.PlaceVoteResultModel import com.moive.app.data.votingstatus.model.ScheduleVoteResultModel import com.moive.app.data.votingstatus.remote.datasource.VoteStatusRemoteDataSource import javax.inject.Inject @@ -17,4 +18,11 @@ class VoteStatusRepositoryImpl @Inject constructor( .checkData() .toModel() } + + override suspend fun getPlaceVoteResult(meetingId: Long): Result = + suspendRunCatching { + voteStatusRemoteDataSource.getPlaceVoteResult(meetingId) + .checkData() + .toModel() + } } From e30cb2273681ef82e1911deac8288a0c93f2dcc4 Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:29:27 +0900 Subject: [PATCH 15/17] =?UTF-8?q?[feat/#109]=20=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20=ED=98=84=ED=99=A9=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../votestatus/VoteStatusContract.kt | 11 ++++++++ .../votestatus/VoteStatusViewModel.kt | 28 +++++++++++++++++++ .../votestatus/component/PlaceVoteSection.kt | 2 +- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusContract.kt b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusContract.kt index 9acea366..4526b08c 100644 --- a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusContract.kt +++ b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusContract.kt @@ -21,6 +21,8 @@ interface VoteStatusContract { ScheduleVoteCandidateModel(meetingDate = "2026-09-08", meetingTime = "18:00", voterCount = 3, isVotedByMe = true), ScheduleVoteCandidateModel(meetingDate = "2026-09-10", meetingTime = "18:00", voterCount = 3, isVotedByMe = false), ), + val placeVoteResultUiState: PlaceVoteResultUiState = PlaceVoteResultUiState.Idle, + val isPlaceVoteFinished: Boolean = false, val placeTotalVoterCount: Int = 4, val placeCandidates: ImmutableList = persistentListOf( PlaceVoteCandidateModel(id = 1L, placeName = "OOO 맛집", voterCount = 4, isVotedByMe = true), @@ -88,3 +90,12 @@ sealed interface ScheduleVoteResultUiState { val msg: String, ) : ScheduleVoteResultUiState } + +sealed interface PlaceVoteResultUiState { + data object Idle : PlaceVoteResultUiState + data object Loading : PlaceVoteResultUiState + data object Success : PlaceVoteResultUiState + data class Failure( + val msg: String, + ) : PlaceVoteResultUiState +} diff --git a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt index bf783a75..fe3062b8 100644 --- a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt +++ b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt @@ -29,6 +29,7 @@ class VoteStatusViewModel @Inject constructor( init { getScheduleVoteResult() + getPlaceVoteResult() } private fun getScheduleVoteResult() = viewModelScope.launch { @@ -57,6 +58,32 @@ class VoteStatusViewModel @Inject constructor( } } + private fun getPlaceVoteResult() = viewModelScope.launch { + _uiState.update { it.copy(placeVoteResultUiState = PlaceVoteResultUiState.Loading) } + + voteStatusRepository.getPlaceVoteResult(meetingId) + .onSuccess { result -> + _uiState.update { + it.copy( + placeVoteResultUiState = PlaceVoteResultUiState.Success, + isPlaceVoteFinished = result.isFinished, + placeTotalVoterCount = result.totalVoterCount, + placeCandidates = result.candidates.toImmutableList(), + ) + } + } + .onFailure { error -> + Timber.tag(TAG).e(error, PLACE_VOTE_RESULT_FAILURE_MESSAGE) + _uiState.update { + it.copy( + placeVoteResultUiState = PlaceVoteResultUiState.Failure( + error.message ?: UNKNOWN_ERROR_MESSAGE, + ), + ) + } + } + } + fun onPlaceItemClick(placeId: Long) { _uiState.update { it.copy(step = Step.DETAIL, currentPlaceId = placeId) } } @@ -74,6 +101,7 @@ class VoteStatusViewModel @Inject constructor( private const val TAG = "VoteStatus" private const val KAKAO_MAP_ERROR = "카카오 맵을 열 수 없습니다." private const val SCHEDULE_VOTE_RESULT_FAILURE_MESSAGE = "일정 투표 현황 조회에 실패했습니다." + private const val PLACE_VOTE_RESULT_FAILURE_MESSAGE = "장소 투표 현황 조회에 실패했습니다." private const val UNKNOWN_ERROR_MESSAGE = "알 수 없는 에러가 발생했습니다." } } diff --git a/app/src/main/java/com/moive/app/presentation/votestatus/component/PlaceVoteSection.kt b/app/src/main/java/com/moive/app/presentation/votestatus/component/PlaceVoteSection.kt index f75c73c7..2c794424 100644 --- a/app/src/main/java/com/moive/app/presentation/votestatus/component/PlaceVoteSection.kt +++ b/app/src/main/java/com/moive/app/presentation/votestatus/component/PlaceVoteSection.kt @@ -38,7 +38,7 @@ fun PlaceVoteSection( ) { top3.forEach { candidate -> VoteOptionRow( - text = candidate.placeName, + text = candidate.placeName ?: "알 수 없는 장소", voteRatio = voteRatio(candidate.voterCount, totalVoterCount), isTopVote = candidate.voterCount == topVoterCount, isVotedByMe = candidate.isVotedByMe, From 40872080e457928d3889e95422e1b016843670f8 Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:39:23 +0900 Subject: [PATCH 16/17] =?UTF-8?q?[feat/#109]=20=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=ED=98=84=ED=99=A9=20->=20=EC=9E=A5=EC=86=8C=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=9D=B4=EB=8F=99=20=EA=B2=BD=EB=A1=9C=20api=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../votestatus/VoteStatusContract.kt | 54 +++++++++-------- .../votestatus/VoteStatusScreen.kt | 15 ++--- .../votestatus/VoteStatusViewModel.kt | 58 ++++++++++++++++++- 3 files changed, 92 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusContract.kt b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusContract.kt index 4526b08c..ba9fab0a 100644 --- a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusContract.kt +++ b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusContract.kt @@ -29,41 +29,36 @@ interface VoteStatusContract { PlaceVoteCandidateModel(id = 2L, placeName = "XXX 카페", voterCount = 3, isVotedByMe = true), PlaceVoteCandidateModel(id = 3L, placeName = "ΔΔΔ 이자카야", voterCount = 2, isVotedByMe = false), ), + val placeRouteUiState: PlaceRouteUiState = PlaceRouteUiState.Idle, val currentPlaceId: Long? = null, val currentPlaceDetail: PlaceDetailModel = PlaceDetailModel( id = 1L, - userName = "모이브", - placeName = "장소명(상호명)", - category = "카페", - address = "서울시 강남구 워시기워시기 123", - areaName = "신논현동", - totalMemberCount = 7, - matchMemberCount = 4, + userName = "", + placeName = "", + category = "", + address = "", + areaName = "", + totalMemberCount = 0, + matchMemberCount = 0, imageList = emptyList(), startPinLatLang = PlaceDetailPinLatLang( - latitude = 37.5044, - longitude = 127.0246, + latitude = 0.0, + longitude = 0.0, ), endPinLatLang = PlaceDetailPinLatLang( - latitude = 37.5089, - longitude = 127.0632, + latitude = 0.0, + longitude = 0.0, ), routeLatLang = PlaceDetailRouteLatLang( - latitude = listOf( - 37.5044, 37.5054217, 37.505525, 37.5049788, 37.50665, - 37.5083212, 37.507775, 37.5078783, 37.5089, - ), - longitude = listOf( - 127.0246, 127.0291954, 127.03425, 127.0396293, 127.0439, - 127.0481707, 127.05355, 127.0586046, 127.0632, - ), + latitude = listOf(0.0, 0.0), + longitude = listOf(0.0, 0.0), ), - totalTravelMinutes = 34, - avgTravelMinutes = 36, - walkMinutes = 10, - busMinutes = 15, - subwayMinutes = 9, - travelFare = 1_650, + totalTravelMinutes = 0, + avgTravelMinutes = 0, + walkMinutes = 0, + busMinutes = 0, + subwayMinutes = 0, + travelFare = 0, ), ) { val scheduleTopVoterCount: Int @@ -99,3 +94,12 @@ sealed interface PlaceVoteResultUiState { val msg: String, ) : PlaceVoteResultUiState } + +sealed interface PlaceRouteUiState { + data object Idle : PlaceRouteUiState + data object Loading : PlaceRouteUiState + data object Success : PlaceRouteUiState + data class Failure( + val msg: String, + ) : PlaceRouteUiState +} diff --git a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusScreen.kt b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusScreen.kt index a7ed6026..089c2ce4 100644 --- a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusScreen.kt +++ b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusScreen.kt @@ -19,7 +19,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.moive.app.core.designsystem.component.topbar.MoiveSubTitleTopBar import com.moive.app.core.designsystem.theme.MoiveTheme import com.moive.app.core.designsystem.theme.MoiveTheme.colors -import com.moive.app.core.extensions.openKakaoMapRoute +import com.moive.app.core.extensions.openUrl import com.moive.app.data.votingstatus.model.ScheduleVoteCandidateModel import com.moive.app.presentation.common.component.placedetail.PlaceDetailContent import com.moive.app.presentation.votestatus.VoteStatusContract.Step @@ -48,14 +48,11 @@ fun VoteStatusRoute( onPlaceItemClick = viewModel::onPlaceItemClick, onDetailBackClick = viewModel::backToList, onKakaoMapClick = { - val place = uiState.currentPlaceDetail - val opened = context.openKakaoMapRoute( - startLatitude = place.startPinLatLang.latitude, - startLongitude = place.startPinLatLang.longitude, - endLatitude = place.endPinLatLang.latitude, - endLongitude = place.endPinLatLang.longitude, - ) - viewModel.onKakaoMapRouteOpened(opened) + val landingUrl = uiState.currentPlaceDetail.landingUrl + if (landingUrl.isNotBlank()) { + val opened = context.openUrl(landingUrl) + viewModel.onKakaoMapRouteOpened(opened) + } }, modifier = modifier, ) diff --git a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt index fe3062b8..7cd089d5 100644 --- a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt +++ b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt @@ -4,11 +4,13 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.moive.app.data.voting.repository.VotingRepository import com.moive.app.data.votingstatus.repository.VoteStatusRepository import com.moive.app.presentation.votestatus.VoteStatusContract.Step import com.moive.app.presentation.votestatus.navigation.VoteStatus import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update @@ -20,6 +22,7 @@ import javax.inject.Inject class VoteStatusViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val voteStatusRepository: VoteStatusRepository, + private val votingRepository: VotingRepository, ) : ViewModel() { private val meetingId: Long = savedStateHandle.toRoute().meetingId @@ -27,6 +30,8 @@ class VoteStatusViewModel @Inject constructor( private val _uiState = MutableStateFlow(VoteStatusContract.State()) val uiState = _uiState.asStateFlow() + private var placeRouteJob: Job? = null + init { getScheduleVoteResult() getPlaceVoteResult() @@ -85,7 +90,56 @@ class VoteStatusViewModel @Inject constructor( } fun onPlaceItemClick(placeId: Long) { - _uiState.update { it.copy(step = Step.DETAIL, currentPlaceId = placeId) } + placeRouteJob?.cancel() + + val candidateName = _uiState.value.placeCandidates.firstOrNull { it.id == placeId }?.placeName + + _uiState.update { + it.copy( + step = Step.DETAIL, + currentPlaceId = placeId, + currentPlaceDetail = it.currentPlaceDetail.copy( + id = placeId, + placeName = candidateName ?: UNKNOWN_PLACE_NAME, + ), + ) + } + + placeRouteJob = viewModelScope.launch { + fetchPlaceRoute(placeId) + } + } + + private suspend fun fetchPlaceRoute(placeId: Long) { + _uiState.update { it.copy(placeRouteUiState = PlaceRouteUiState.Loading) } + + votingRepository.getRecommendedPlaceRoute( + meetingId = meetingId, + recommendedPlaceId = placeId, + current = _uiState.value.currentPlaceDetail, + ) + .onSuccess { updated -> + if (_uiState.value.currentPlaceId != placeId) return@onSuccess + + _uiState.update { + it.copy( + placeRouteUiState = PlaceRouteUiState.Success, + currentPlaceDetail = updated, + ) + } + } + .onFailure { error -> + if (_uiState.value.currentPlaceId != placeId) return@onFailure + + Timber.tag(TAG).e(error, PLACE_ROUTE_FAILURE_MESSAGE) + _uiState.update { + it.copy( + placeRouteUiState = PlaceRouteUiState.Failure( + error.message ?: UNKNOWN_ERROR_MESSAGE, + ), + ) + } + } } fun backToList() { @@ -102,6 +156,8 @@ class VoteStatusViewModel @Inject constructor( private const val KAKAO_MAP_ERROR = "카카오 맵을 열 수 없습니다." private const val SCHEDULE_VOTE_RESULT_FAILURE_MESSAGE = "일정 투표 현황 조회에 실패했습니다." private const val PLACE_VOTE_RESULT_FAILURE_MESSAGE = "장소 투표 현황 조회에 실패했습니다." + private const val PLACE_ROUTE_FAILURE_MESSAGE = "이동 경로 조회에 실패했습니다." private const val UNKNOWN_ERROR_MESSAGE = "알 수 없는 에러가 발생했습니다." + private const val UNKNOWN_PLACE_NAME = "알 수 없는 장소" } } From 8b728c4129ee75b71f10882f9b11816c7c372e48 Mon Sep 17 00:00:00 2001 From: vahkjsdf Date: Sun, 13 Sep 2026 02:57:44 +0900 Subject: [PATCH 17/17] =?UTF-8?q?[mod/#109]=20=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20=EC=8B=9C=20?= =?UTF-8?q?api=20=ED=98=B8=EC=B6=9C=20=EC=A0=84=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/votestatus/VoteStatusViewModel.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt index 7cd089d5..cf9e5321 100644 --- a/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt +++ b/app/src/main/java/com/moive/app/presentation/votestatus/VoteStatusViewModel.kt @@ -4,6 +4,8 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.moive.app.data.voting.model.PlaceDetailPinLatLang +import com.moive.app.data.voting.model.PlaceDetailRouteLatLang import com.moive.app.data.voting.repository.VotingRepository import com.moive.app.data.votingstatus.repository.VoteStatusRepository import com.moive.app.presentation.votestatus.VoteStatusContract.Step @@ -101,6 +103,17 @@ class VoteStatusViewModel @Inject constructor( currentPlaceDetail = it.currentPlaceDetail.copy( id = placeId, placeName = candidateName ?: UNKNOWN_PLACE_NAME, + userName = "", + startPinLatLang = PlaceDetailPinLatLang(latitude = 0.0, longitude = 0.0), + endPinLatLang = PlaceDetailPinLatLang(latitude = 0.0, longitude = 0.0), + routeLatLang = PlaceDetailRouteLatLang(latitude = listOf(0.0, 0.0), longitude = listOf(0.0, 0.0)), + totalTravelMinutes = 0, + avgTravelMinutes = 0, + walkMinutes = 0, + busMinutes = 0, + subwayMinutes = 0, + travelFare = 0, + landingUrl = "", ), ) }