From 7f8235c6f0f437bf49e1cdb659f483fcaadbec8f Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Thu, 16 Jul 2026 21:13:10 +0900 Subject: [PATCH 01/19] =?UTF-8?q?[mod/#216]=20profileImageUrl=20=ED=95=84?= =?UTF-8?q?=EC=88=98=EC=9D=B8=EC=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../remote/dto/response/ConsumerLessonDetailResponse.kt | 4 ++-- .../consumer/repository/impl/ConsumerLessonRepositoryImpl.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/src/main/java/com/ssing/data/lesson/consumer/remote/dto/response/ConsumerLessonDetailResponse.kt b/data/src/main/java/com/ssing/data/lesson/consumer/remote/dto/response/ConsumerLessonDetailResponse.kt index c3313b86..c7282185 100644 --- a/data/src/main/java/com/ssing/data/lesson/consumer/remote/dto/response/ConsumerLessonDetailResponse.kt +++ b/data/src/main/java/com/ssing/data/lesson/consumer/remote/dto/response/ConsumerLessonDetailResponse.kt @@ -69,7 +69,7 @@ data class InstructorProfileResponse( @SerialName("gender") val gender: String, @SerialName("birthYear") val birthYear: Int, @SerialName("level") val level: Int, - @SerialName("profileImageUrl") val profileImageUrl: String? = null, + @SerialName("profileImageUrl") val profileImageUrl: String, ) @Serializable @@ -156,4 +156,4 @@ data class ConsumerMatchingRequestResponse( @SerialName("representativeMemberName") val representativeMemberName: String, @SerialName("headcount") val headcount: Int, @SerialName("participants") val participants: List, -) \ No newline at end of file +) diff --git a/data/src/main/java/com/ssing/data/lesson/consumer/repository/impl/ConsumerLessonRepositoryImpl.kt b/data/src/main/java/com/ssing/data/lesson/consumer/repository/impl/ConsumerLessonRepositoryImpl.kt index 1229f55e..b0c26b1f 100644 --- a/data/src/main/java/com/ssing/data/lesson/consumer/repository/impl/ConsumerLessonRepositoryImpl.kt +++ b/data/src/main/java/com/ssing/data/lesson/consumer/repository/impl/ConsumerLessonRepositoryImpl.kt @@ -144,7 +144,7 @@ internal class ConsumerLessonRepositoryImpl @Inject constructor( gender = gender, birthYear = birthYear, level = level, - profileImageUrl = profileImageUrl.orEmpty(), + profileImageUrl = profileImageUrl, ) private fun ParticipantResponse.toModel() = LessonParticipant(participantId, gender, age) @@ -176,4 +176,4 @@ internal class ConsumerLessonRepositoryImpl @Inject constructor( ) } -} \ No newline at end of file +} From dc1eaefdfb40add8efc6e2c82b90390ae5ce65c8 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Thu, 16 Jul 2026 22:23:09 +0900 Subject: [PATCH 02/19] =?UTF-8?q?[fix/#216]=20=EB=A7=A4=EC=B9=AD=20?= =?UTF-8?q?=EB=AF=B8=EC=8B=9C=EC=9E=91=20=EA=B0=95=EC=82=AC=20=ED=99=88=20?= =?UTF-8?q?=EC=A7=84=EC=9E=85=20=EC=8B=9C=20409=20=ED=86=A0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 매칭을 시작한 적 없는 강사가 매칭 화면 진입 시 fetchMatchingActive/fetchOfferDetail이 409(MATCHING_NOT_ACTIVE)를 반환하면 토스트 없이 SettingExposure로 전환. 소비자가 결제 취소 시 OfferReceivedEvent(PendingConfirm) → restoreActiveOffer()로 변경해 stale offerId로 인한 409 토스트도 제거. --- .../presentation/instructormatching/MatchingViewModel.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt index 3af5ae54..de568a5c 100644 --- a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt +++ b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt @@ -279,7 +279,9 @@ internal class MatchingViewModel @Inject constructor( } .onFailure { Timber.e(it, "matching-offers 실패") - if (it is ApiException) { + if (it is ApiException.Conflict) { + updateState { copy(phase = MatchingPhase.SettingExposure) } + } else if (it is ApiException) { sendEffect(MatchingContract.Effect.ShowToast(it.uiMessage)) } } @@ -302,7 +304,9 @@ internal class MatchingViewModel @Inject constructor( } .onFailure { Timber.e(it, "matching-offer 상세 실패") - if (it is ApiException) { + if (it is ApiException.Conflict) { + updateState { copy(phase = MatchingPhase.SettingExposure) } + } else if (it is ApiException) { sendEffect(MatchingContract.Effect.ShowToast(it.uiMessage)) } } From 678f579215e4002c47a1b00436b905ca67e5a27c Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Thu, 16 Jul 2026 22:23:18 +0900 Subject: [PATCH 03/19] =?UTF-8?q?[fix/#216]=20=EA=B0=95=EC=82=AC=20?= =?UTF-8?q?=ED=99=88=20init=20=EC=B5=9C=EC=B4=88=20=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20(ON=5FRESUME=EC=9C=BC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EA=B4=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/instructorhome/InstructorHomeViewModel.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt index 118cdea4..9c09bf4a 100644 --- a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt +++ b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt @@ -29,10 +29,6 @@ internal class InstructorHomeViewModel @Inject constructor( InstructorHomeContract.State() ) { - init { - loadHome() - } - fun loadHome() { viewModelScope.launch { updateState { From fdf56e173a8d7a75f84599f28cb3d62db9e85a3c Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Thu, 16 Jul 2026 22:23:26 +0900 Subject: [PATCH 04/19] =?UTF-8?q?[fix/#216]=20=EA=B0=95=EC=82=AC=20?= =?UTF-8?q?=ED=99=88=20ON=5FRESUME=EB=A7=88=EB=8B=A4=20=ED=99=88=20?= =?UTF-8?q?=EC=9E=AC=EC=A1=B0=ED=9A=8C=20(=EB=A7=A4=EC=B9=AD=20=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20=ED=9B=84=20=EC=B9=B4=EB=93=9C=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/instructorhome/InstructorHomeScreen.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt index 64211d69..e4abbf9a 100644 --- a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt +++ b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt @@ -24,6 +24,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.ssing.core.ui.R import com.ssing.core.ui.common.component.Empty @@ -55,6 +57,10 @@ internal fun InstructorHomeRoute( val state by viewModel.uiState.collectAsStateWithLifecycle() val context = LocalContext.current + LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { + viewModel.loadHome() + } + HandleUiEffects(viewModel.uiEffect) { effect -> when (effect) { is InstructorHomeContract.Effect.NavigateToMatchingWaiting -> navigateToMatchingWaiting From 9e0b8950cdf6d8349925f8ad86b477db029cdf65 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Thu, 16 Jul 2026 22:41:53 +0900 Subject: [PATCH 05/19] =?UTF-8?q?[fix/#204]=20=EC=A6=89=EC=8B=9C=EB=A7=A4?= =?UTF-8?q?=EC=B9=AD=20=EC=8B=9C=EC=9E=91=20=EC=8B=9C=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95(I05)=20=EA=B1=B4=EB=84=88=EB=9B=B0?= =?UTF-8?q?=EA=B3=A0=20=EC=9D=B4=EC=A0=84=20=EC=A1=B0=EA=B1=B4=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=A7=A4=EC=B9=AD=EB=90=98=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - InstructorMatching 라우트에 startFresh 플래그 추가, init에서 startFresh면 상태 복구 스킵(I05 유지) - 홈 '즉시매칭 시작'(promo) 진입은 startFresh=true로 조건 설정 화면 유지 - 홈 활성 매칭 카드(MATCHING/제안단계)는 NavigateToMatchingWaiting으로 상태 복구 진입 - NavHost의 InstructorMatching() 괄호 누락, 홈 화면 navigateToMatchingWaiting() 호출 괄호 누락(미동작) 수정 --- .../com/ssing/instructor/InstructorMainNavHost.kt | 4 ++-- .../instructorhome/InstructorHomeScreen.kt | 2 +- .../instructorhome/InstructorHomeViewModel.kt | 4 ++-- .../instructormatching/MatchingViewModel.kt | 12 +++++------- .../navigation/InstructorMatchingNavigation.kt | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt b/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt index 5d4fd555..8ae2e6b6 100644 --- a/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt +++ b/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt @@ -55,7 +55,7 @@ internal fun InstructorMainNavHost( paddingValues = paddingValues, navigateToMatching = { navController.navigate( - route = InstructorMatching(), + route = InstructorMatching(startFresh = true), ) }, navigateToLessonDetail = { lessonId -> @@ -65,7 +65,7 @@ internal fun InstructorMainNavHost( }, navigateToMatchingWaiting = { navController.navigate( - route = InstructorMatching + route = InstructorMatching(), ) }, navigateToNotification = { diff --git a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt index e4abbf9a..7b72e0f0 100644 --- a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt +++ b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt @@ -63,7 +63,7 @@ internal fun InstructorHomeRoute( HandleUiEffects(viewModel.uiEffect) { effect -> when (effect) { - is InstructorHomeContract.Effect.NavigateToMatchingWaiting -> navigateToMatchingWaiting + is InstructorHomeContract.Effect.NavigateToMatchingWaiting -> navigateToMatchingWaiting() is InstructorHomeContract.Effect.NavigateToLessonDetail -> navigateToLessonDetail(effect.lessonId) is InstructorHomeContract.Effect.NavigateToMatching -> navigateToMatching() is InstructorHomeContract.Effect.ShowToast -> context.toast(effect.message) diff --git a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt index 9c09bf4a..6832ff5e 100644 --- a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt +++ b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt @@ -136,7 +136,7 @@ internal class InstructorHomeViewModel @Inject constructor( when (lesson.status) { Status.Matching -> { sendEffect( - InstructorHomeContract.Effect.NavigateToMatching + InstructorHomeContract.Effect.NavigateToMatchingWaiting ) } @@ -144,7 +144,7 @@ internal class InstructorHomeViewModel @Inject constructor( val lessonId = lesson.lessonId if (lessonId == null) { - sendEffect(InstructorHomeContract.Effect.NavigateToMatching) + sendEffect(InstructorHomeContract.Effect.NavigateToMatchingWaiting) return } diff --git a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt index de568a5c..9678f9f2 100644 --- a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt +++ b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt @@ -46,13 +46,11 @@ internal class MatchingViewModel @Inject constructor( init { loadMatchingExposure() - // FCM '새 강습 도착' 딥링크로 진입하면 offerId가 담겨온다. - // offerId가 있으면 그 제안 상세를, 없으면(거절/일반 진입) 활성 제안을 조회한다. - val offerId = savedStateHandle.toRoute().offerId - if (offerId != null) { - restoreOfferDetail(offerId) - } else { - restoreActiveOffer() + val route = savedStateHandle.toRoute() + when { + route.offerId != null -> restoreOfferDetail(route.offerId) + route.startFresh -> Unit + else -> restoreActiveOffer() } instructorMatchingRepository.connectSocket() diff --git a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/navigation/InstructorMatchingNavigation.kt b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/navigation/InstructorMatchingNavigation.kt index ea38e6d9..98d73d49 100644 --- a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/navigation/InstructorMatchingNavigation.kt +++ b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/navigation/InstructorMatchingNavigation.kt @@ -17,7 +17,7 @@ import kotlinx.serialization.Serializable * @param offerId FCM '새 강습 도착' 딥링크로 진입 시 특정 제안 id. 그 외(거절/일반 진입)엔 null. */ @Serializable -data class InstructorMatching(val offerId: Long? = null) : Route +data class InstructorMatching(val offerId: Long? = null, val startFresh: Boolean = false) : Route fun NavGraphBuilder.instructorMatchingNavGraph( paddingValues: PaddingValues, From 318b502e39b5cf581e293c3114f591ce19a9b806 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 00:31:04 +0900 Subject: [PATCH 06/19] =?UTF-8?q?[fix/#204]=20=EB=A7=A4=EC=B9=AD=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A0=95=EC=82=B0=20=EA=B8=88=EC=95=A1=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=A0=95=EB=A0=AC=20=EB=B0=8F=20=EC=A2=85?= =?UTF-8?q?=EB=A3=8C=20offer=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - priceSummary를 강사 정산 금액(instructorSettlementAmount)로 정렬 (기존 소비자용 lessonPriceAmount/resortPassFeeAmount/totalPaymentAmount 제거) → MissingFieldException 파싱 크래시 해결 - 종료된 offer 조회 시 409 MATCHING_NOT_ACTIVE를 홈 재조회로 처리(확정 강습이면 lessonId 이동, 아니면 대기) - 강습 확정 시 즉시노출 중단(cancelMatchingExposure) 호출로 신규 매칭 유입 차단(정상 플로우 한정, 서버 보강 TODO) --- .../model/InstructorMatchingOffer.kt | 4 +--- .../dto/response/InstructorMatchingOffersResponse.kt | 4 +--- .../impl/InstructorMatchingRepositoryImpl.kt | 4 +--- .../instructormatching/MatchingViewModel.kt | 12 +++++++++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/data/src/main/java/com/ssing/data/matching/instructormatching/model/InstructorMatchingOffer.kt b/data/src/main/java/com/ssing/data/matching/instructormatching/model/InstructorMatchingOffer.kt index bb108af3..08c3cb5e 100644 --- a/data/src/main/java/com/ssing/data/matching/instructormatching/model/InstructorMatchingOffer.kt +++ b/data/src/main/java/com/ssing/data/matching/instructormatching/model/InstructorMatchingOffer.kt @@ -16,7 +16,5 @@ data class InstructorMatchingLessonSummary( ) data class InstructorMatchingPriceSummary( - val lessonPriceAmount: Int, - val resortPassFeeAmount: Int, - val totalPaymentAmount: Int, + val instructorSettlementAmount: Int, ) diff --git a/data/src/main/java/com/ssing/data/matching/instructormatching/remote/dto/response/InstructorMatchingOffersResponse.kt b/data/src/main/java/com/ssing/data/matching/instructormatching/remote/dto/response/InstructorMatchingOffersResponse.kt index f286cb07..68b645b4 100644 --- a/data/src/main/java/com/ssing/data/matching/instructormatching/remote/dto/response/InstructorMatchingOffersResponse.kt +++ b/data/src/main/java/com/ssing/data/matching/instructormatching/remote/dto/response/InstructorMatchingOffersResponse.kt @@ -39,7 +39,5 @@ internal data class InstructorMatchingLessonSummaryResponse( @Serializable internal data class InstructorMatchingPriceSummaryResponse( - @SerialName("lessonPriceAmount") val lessonPriceAmount: Int, - @SerialName("resortPassFeeAmount") val resortPassFeeAmount: Int, - @SerialName("totalPaymentAmount") val totalPaymentAmount: Int, + @SerialName("instructorSettlementAmount") val instructorSettlementAmount: Int? = null, ) diff --git a/data/src/main/java/com/ssing/data/matching/instructormatching/repository/impl/InstructorMatchingRepositoryImpl.kt b/data/src/main/java/com/ssing/data/matching/instructormatching/repository/impl/InstructorMatchingRepositoryImpl.kt index 4e96e9aa..5932d9f9 100644 --- a/data/src/main/java/com/ssing/data/matching/instructormatching/repository/impl/InstructorMatchingRepositoryImpl.kt +++ b/data/src/main/java/com/ssing/data/matching/instructormatching/repository/impl/InstructorMatchingRepositoryImpl.kt @@ -237,9 +237,7 @@ internal class InstructorMatchingRepositoryImpl @Inject constructor( }, priceSummary = requireNotNull(priceSummary) { "AVAILABLE 응답에 priceSummary 누락" }.let { InstructorMatchingPriceSummary( - lessonPriceAmount = it.lessonPriceAmount, - resortPassFeeAmount = it.resortPassFeeAmount, - totalPaymentAmount = it.totalPaymentAmount, + instructorSettlementAmount = it.instructorSettlementAmount ?: 0, ) }, participants = participants.map { diff --git a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt index 9678f9f2..03612a75 100644 --- a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt +++ b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt @@ -101,7 +101,12 @@ internal class MatchingViewModel @Inject constructor( } private fun onMatchingConfirmed(lessonId: Long) = viewModelScope.launch { - Timber.d("매칭 확정 수신 → 강습 상세 이동 (lessonId=$lessonId)") + Timber.d("매칭 확정 수신 → 노출 중단 후 강습 상세 이동 (lessonId=$lessonId)") + // TODO(서버 보강 필요): 이 처리는 '확정 순간 매칭 화면(소켓 연결)에 있을 때'만 동작한다. + // 강사가 홈 등으로 이동해 소켓이 끊긴 뒤 확정되면(FCM만 수신) 노출이 안 꺼질 수 있으므로, + // 완전한 신규 매칭 차단은 서버가 '확정 강습 보유 강사 노출 제외'로 처리해야 한다. + instructorMatchingRepository.cancelMatchingExposure() + .onFailure { Timber.e(it, "확정 후 노출 중단 실패") } try { instructorMatchingRepository.disconnectSocket() } finally { @@ -303,7 +308,8 @@ internal class MatchingViewModel @Inject constructor( .onFailure { Timber.e(it, "matching-offer 상세 실패") if (it is ApiException.Conflict) { - updateState { copy(phase = MatchingPhase.SettingExposure) } + // 409 MATCHING_NOT_ACTIVE: 협상 종료 → 홈 재조회 후 확정 강습이면 이동, 아니면 대기 + navigateToLessonIfConfirmed(offerId) } else if (it is ApiException) { sendEffect(MatchingContract.Effect.ShowToast(it.uiMessage)) } @@ -372,7 +378,7 @@ internal class MatchingViewModel @Inject constructor( expiresAtMillis = null, nickname = requestSummary.requesterName, teamCount = requestSummary.headcount, - price = priceSummary.totalPaymentAmount, + price = priceSummary.instructorSettlementAmount, participants = participants.map { ParticipantUiModel(age = it.age, isMale = it.gender == GENDER_MALE) }, From 077a5a4b3e8dace5e02361cc3ad43a6a522f7fe8 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 00:31:04 +0900 Subject: [PATCH 07/19] =?UTF-8?q?[fix/#204]=20=ED=99=95=EC=A0=95/=EC=A7=84?= =?UTF-8?q?=ED=96=89=20=EA=B0=95=EC=8A=B5=20=EB=B3=B4=EC=9C=A0=20=EC=8B=9C?= =?UTF-8?q?=20=ED=99=88=20=EC=A6=89=EC=8B=9C=EB=A7=A4=EC=B9=AD=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=20=EC=B0=A8=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 홈 응답 lessonCards에 CONFIRMED/IN_PROGRESS가 있으면 hasActiveLesson=true로 판정 - 즉시매칭 시작(onMatchingClick) 시 hasActiveLesson이면 토스트로 안내하고 진입 차단 --- .../instructorhome/InstructorHomeContract.kt | 1 + .../instructorhome/InstructorHomeViewModel.kt | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeContract.kt b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeContract.kt index 71c054a5..dfaa1b6c 100644 --- a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeContract.kt +++ b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeContract.kt @@ -19,6 +19,7 @@ internal interface InstructorHomeContract { val averageRating: Float = 0f, val grade: Grade = Grade.GRADE1, val achievementRate: Int = 0, + val hasActiveLesson: Boolean = false, ) sealed interface Effect { diff --git a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt index 6832ff5e..5d8bfe99 100644 --- a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt +++ b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt @@ -43,6 +43,9 @@ internal class InstructorHomeViewModel @Inject constructor( copy( isLoading = false, lessonCards = result.lessonCards.toUiState(), + hasActiveLesson = result.lessonCards.any { + it.displayStatus == CONFIRMED || it.displayStatus == IN_PROGRESS + }, hasUnreadNotification = result.hasUnreadNotification, instructorName = result.instructorName, matchingPeopleCount = result.matchingPeopleCount, @@ -122,9 +125,17 @@ internal class InstructorHomeViewModel @Inject constructor( const val PAYMENT_PENDING = "PAYMENT_PENDING" const val CONFIRMED = "CONFIRMED" const val IN_PROGRESS = "IN_PROGRESS" + + const val MSG_ACTIVE_LESSON_BLOCK = + "진행 예정이거나 진행 중인 강습이 있어 즉시매칭을 시작할 수 없어요." } fun onMatchingClick() { + // 확정/진행 중인 강습이 있으면 즉시매칭 시작을 막는다. + if (uiState.value.hasActiveLesson) { + sendEffect(InstructorHomeContract.Effect.ShowToast(MSG_ACTIVE_LESSON_BLOCK)) + return + } sendEffect(InstructorHomeContract.Effect.NavigateToMatching) } From 755a2ae6df7d222d96e539b0661ccbe3101949a6 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 00:42:52 +0900 Subject: [PATCH 08/19] =?UTF-8?q?[fix/#216]=20NavigateToMatchingWaiting?= =?UTF-8?q?=EC=97=90=20offerId=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ssing/presentation/instructorhome/InstructorHomeContract.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeContract.kt b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeContract.kt index dfaa1b6c..2527a1fb 100644 --- a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeContract.kt +++ b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeContract.kt @@ -30,7 +30,7 @@ internal interface InstructorHomeContract { val offerId: Long? = null, ) : Effect - data object NavigateToMatchingWaiting : Effect + data class NavigateToMatchingWaiting(val offerId: Long? = null) : Effect } } From a1603d6ea2bc219b64d2ed00bc6ec442321bb209 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 00:43:00 +0900 Subject: [PATCH 09/19] =?UTF-8?q?[fix/#216]=20=ED=99=88=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=ED=81=B4=EB=A6=AD=20=EB=B6=84=EA=B8=B0=EB=A5=BC=20?= =?UTF-8?q?lessonId/offerId=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../instructorhome/InstructorHomeViewModel.kt | 59 ++++--------------- 1 file changed, 13 insertions(+), 46 deletions(-) diff --git a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt index 5d8bfe99..231f3da1 100644 --- a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt +++ b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt @@ -139,52 +139,19 @@ internal class InstructorHomeViewModel @Inject constructor( sendEffect(InstructorHomeContract.Effect.NavigateToMatching) } - fun onLessonClick( - lesson: Reservation, - ) { - Timber.i("lessonId: ${lesson.lessonId} / offerId: $lesson.offerId") - - when (lesson.status) { - Status.Matching -> { - sendEffect( - InstructorHomeContract.Effect.NavigateToMatchingWaiting - ) - } - - Status.Matched -> { - val lessonId = lesson.lessonId - - if (lessonId == null) { - sendEffect(InstructorHomeContract.Effect.NavigateToMatchingWaiting) - return - } - - sendEffect( - InstructorHomeContract.Effect.NavigateToLessonDetail( - lessonId = lessonId, - ) - ) - } - - Status.Default -> { - val lessonId = lesson.lessonId - val offerId = lesson.offerId - - if (lessonId == null || offerId == null) { - Timber.e( - "강습 상세 이동에 필요한 lessonId나 offerId가 없습니다. " + - "status=${lesson.status}, offerId=${lesson.offerId}" - ) - return - } - - sendEffect( - InstructorHomeContract.Effect.NavigateToLessonDetail( - lessonId = lessonId, - offerId = offerId, - ) - ) - } + fun onLessonClick(lesson: Reservation) { + Timber.i("lessonId: ${lesson.lessonId} / offerId: ${lesson.offerId}") + + when { + lesson.lessonId != null -> sendEffect( + InstructorHomeContract.Effect.NavigateToLessonDetail(lessonId = lesson.lessonId) + ) + lesson.offerId != null -> sendEffect( + InstructorHomeContract.Effect.NavigateToMatchingWaiting(offerId = lesson.offerId) + ) + else -> sendEffect( + InstructorHomeContract.Effect.NavigateToMatchingWaiting(offerId = null) + ) } } From fc72bf775a6de3cf67d5c15d5396c421aae2c467 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 00:43:10 +0900 Subject: [PATCH 10/19] =?UTF-8?q?[fix/#216]=20NavigateToMatchingWaiting=20?= =?UTF-8?q?offerId=20=EC=A0=84=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ssing/presentation/instructorhome/InstructorHomeScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt index 7b72e0f0..8c265c8b 100644 --- a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt +++ b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt @@ -52,7 +52,7 @@ internal fun InstructorHomeRoute( navigateToNotification: () -> Unit, modifier: Modifier = Modifier, viewModel: InstructorHomeViewModel = hiltViewModel(), - navigateToMatchingWaiting: () -> Unit, + navigateToMatchingWaiting: (Long?) -> Unit, ) { val state by viewModel.uiState.collectAsStateWithLifecycle() val context = LocalContext.current @@ -63,7 +63,7 @@ internal fun InstructorHomeRoute( HandleUiEffects(viewModel.uiEffect) { effect -> when (effect) { - is InstructorHomeContract.Effect.NavigateToMatchingWaiting -> navigateToMatchingWaiting() + is InstructorHomeContract.Effect.NavigateToMatchingWaiting -> navigateToMatchingWaiting(effect.offerId) is InstructorHomeContract.Effect.NavigateToLessonDetail -> navigateToLessonDetail(effect.lessonId) is InstructorHomeContract.Effect.NavigateToMatching -> navigateToMatching() is InstructorHomeContract.Effect.ShowToast -> context.toast(effect.message) From d08e6334e3bc13deccec3ed442c2faa61acafa73 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 00:43:18 +0900 Subject: [PATCH 11/19] =?UTF-8?q?[fix/#216]=20navigateToMatchingWaiting=20?= =?UTF-8?q?=EC=8B=9C=EA=B7=B8=EB=8B=88=EC=B2=98=20(Long=3F)=20->=20Unit=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../instructorhome/navigation/InstructorHomeNavigation.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/navigation/InstructorHomeNavigation.kt b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/navigation/InstructorHomeNavigation.kt index 85f67d79..59744ed6 100644 --- a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/navigation/InstructorHomeNavigation.kt +++ b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/navigation/InstructorHomeNavigation.kt @@ -15,7 +15,7 @@ fun NavGraphBuilder.instructorHomeNavGraph( navigateToMatching: () -> Unit, navigateToNotification: () -> Unit, navigateToLessonDetail: (Long?) -> Unit, - navigateToMatchingWaiting: () -> Unit, + navigateToMatchingWaiting: (Long?) -> Unit, ) { composable { InstructorHomeRoute( From 17cac0f304b09cb7fe710d1f3a60fe8c45bb613e Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 00:43:26 +0900 Subject: [PATCH 12/19] =?UTF-8?q?[fix/#216]=20=EB=A7=A4=EC=B9=AD=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20offerId?= =?UTF-8?q?=EB=A5=BC=20InstructorMatching=20=EB=9D=BC=EC=9A=B0=ED=8A=B8?= =?UTF-8?q?=EC=97=90=20=EC=A0=84=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/ssing/instructor/InstructorMainNavHost.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt b/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt index 8ae2e6b6..bfe388e0 100644 --- a/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt +++ b/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt @@ -63,9 +63,9 @@ internal fun InstructorMainNavHost( route = InstructorLesson(lessonId = lessonId ?: 0), ) }, - navigateToMatchingWaiting = { + navigateToMatchingWaiting = { offerId -> navController.navigate( - route = InstructorMatching(), + route = InstructorMatching(offerId = offerId), ) }, navigateToNotification = { From 67edaeb3fa36ac58320b18de494ea57b1ac3de9e Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 00:46:32 +0900 Subject: [PATCH 13/19] =?UTF-8?q?[fix/#204]=20=EC=A1=B0=EA=B1=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=A7=84=EC=9E=85=20=EC=8B=9C=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20=EB=85=B8=EC=B6=9C=20=EC=9D=BC=EC=8B=9C=EC=A4=91?= =?UTF-8?q?=EC=A7=80=20(=EC=88=98=EC=A0=95=20=EC=A4=91=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EC=9C=A0=EC=9E=85=20=EC=B0=A8=EB=8B=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/instructormatching/MatchingViewModel.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt index 03612a75..65eb45de 100644 --- a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt +++ b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt @@ -195,8 +195,12 @@ internal class MatchingViewModel @Inject constructor( } } - fun editExposure() = updateState { - copy(phase = MatchingPhase.SettingExposure) + fun editExposure() { + updateState { copy(phase = MatchingPhase.SettingExposure) } + viewModelScope.launch { + instructorMatchingRepository.cancelMatchingExposure() + .onFailure { Timber.e(it, "조건 수정 진입 - 노출 중단 실패") } + } } fun stopWaiting() = updateState { From 3d3327c7e485b621e5da372fe0dfc1edee0e7831 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 00:59:05 +0900 Subject: [PATCH 14/19] =?UTF-8?q?[fix/#204]=20=EA=B0=95=EC=8A=B5=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EB=92=A4=EB=A1=9C=EA=B0=80=EA=B8=B0=20?= =?UTF-8?q?=ED=99=88=20=EB=B3=B5=EA=B7=80=20(=EB=A7=A4=EC=B9=AD=20?= =?UTF-8?q?=EC=8A=A4=ED=83=9D=20=EC=A0=95=EB=A6=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 강습 상세(진입 시 홈 위까지 백스택 정리(popUpTo(InstructorHome)) - 매칭 확정→상세, 홈 카드→상세, 취소→씽매칭 돌아가기 경로 모두 적용 - 뒤로가기가 이전 매칭 스택이 아닌 항상 I04 홈으로 이동 - 매칭 스택 제거로 잔여 소켓 연결도 함께 정리 --- .../com/ssing/instructor/InstructorMainNavHost.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt b/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt index bfe388e0..22d4c993 100644 --- a/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt +++ b/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.NavHostController +import androidx.navigation.navOptions import com.ssing.core.ui.extension.clearBackStackNavOptions import com.ssing.core.ui.navigation.Route import com.ssing.core.ui.navigation.SsingNavHost @@ -61,6 +62,7 @@ internal fun InstructorMainNavHost( navigateToLessonDetail = { lessonId -> navController.navigate( route = InstructorLesson(lessonId = lessonId ?: 0), + navOptions = navController.backToHomeNavOptions(), ) }, navigateToMatchingWaiting = { offerId -> @@ -82,6 +84,7 @@ internal fun InstructorMainNavHost( navigateToLessonDetail = { lessonId -> navController.navigate( route = InstructorLesson(lessonId = lessonId), + navOptions = navController.backToHomeNavOptions(), ) }, ) @@ -89,7 +92,10 @@ internal fun InstructorMainNavHost( paddingValues = paddingValues, navController = navController, navigateToMatching = { - navController.navigate(route = InstructorMatching()) + navController.navigate( + route = InstructorMatching(), + navOptions = navController.backToHomeNavOptions(), + ) }, ) instructorProfileNavGraph( @@ -103,3 +109,7 @@ internal fun InstructorMainNavHost( ) } } +private fun NavHostController.backToHomeNavOptions() = navOptions { + popUpTo(InstructorHome) { inclusive = false } + launchSingleTop = true +} From 78dae6673d1b206761f84c827411fcf1569a4aa0 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 01:17:44 +0900 Subject: [PATCH 15/19] =?UTF-8?q?[fix/#204]=20=EB=A7=A4=EC=B9=AD=20offer?= =?UTF-8?q?=20=EC=A2=85=EB=A3=8C/=EA=B1=B0=EC=A0=88=20=EC=8B=9C=20?= =?UTF-8?q?=ED=99=9C=EC=84=B1=20=EC=83=81=ED=83=9C=20=EC=9E=AC=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EB=A1=9C=20=EB=8F=99=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 강사 거절 시 offerId 무효화 후 active 재조회 (새 offer 갱신) - OfferClosedEvent도 죽은 offerId 상세조회 대신 restoreActiveOffer()로 현재 활성 상태 재동기화 - offerId를 추적 var로 분리해 진입/조회 시 최신값 유지 --- .../instructormatching/MatchingViewModel.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt index 65eb45de..1dbe4144 100644 --- a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt +++ b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt @@ -43,12 +43,14 @@ internal class MatchingViewModel @Inject constructor( BaseViewModel( MatchingContract.State() ) { + private var offerId: Long? = null init { loadMatchingExposure() val route = savedStateHandle.toRoute() + offerId = route.offerId when { - route.offerId != null -> restoreOfferDetail(route.offerId) + offerId != null -> restoreOfferDetail(requireNotNull(offerId)) route.startFresh -> Unit else -> restoreActiveOffer() } @@ -74,7 +76,7 @@ internal class MatchingViewModel @Inject constructor( restoreOfferDetail(event.offerId) } } - is InstructorMatchingEvent.OfferClosedEvent -> restoreOfferDetail(event.offerId) + is InstructorMatchingEvent.OfferClosedEvent -> restoreActiveOffer() is InstructorMatchingEvent.MatchingCanceledEvent -> { updateState { copy(phase = MatchingPhase.Waiting) } @@ -254,7 +256,11 @@ internal class MatchingViewModel @Inject constructor( instructorMatchingRepository.respondMatchingOffer(offer.offerId, DECISION_REJECTED) .onSuccess { result -> Timber.d("매칭 제안 거절 응답: $result") + // 거절한 offer는 무효화하고, 서버가 새로 매칭한 offer(새 offerId)를 + // active 재조회로 받아온다. (없으면 대기 화면 유지) + offerId = null updateState { copy(phase = MatchingPhase.Waiting) } + restoreActiveOffer() } .onFailure { Timber.e(it, "매칭 제안 거절 실패") @@ -277,9 +283,10 @@ internal class MatchingViewModel @Inject constructor( Timber.d("matching-offers 응답: $active") // 저장된 조건은 항상 복원한다(대기 화면 조건 카드/조건 수정용). updateState { copy(exposure = exposure.applyMatchingSetting(active.setting)) } - val offerId = active.offerId + val activeOfferId = active.offerId + offerId = activeOfferId when { - offerId != null -> restoreOfferDetail(offerId) + activeOfferId != null -> restoreOfferDetail(activeOfferId) active.setting.isExposed -> updateState { copy(phase = MatchingPhase.Waiting) } else -> updateState { copy(phase = MatchingPhase.SettingExposure) } } @@ -296,6 +303,7 @@ internal class MatchingViewModel @Inject constructor( } fun restoreOfferDetail(offerId: Long) { + this.offerId = offerId restoreJob?.cancel() restoreJob = viewModelScope.launch { instructorMatchingRepository.fetchOfferDetail(offerId) From f2aa6fa79cc5f5afeabd2ee7f06ecc083b35ddd9 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 02:01:12 +0900 Subject: [PATCH 16/19] =?UTF-8?q?[fix/#204]=20=ED=99=95=EC=A0=95=20?= =?UTF-8?q?=EC=A0=84=20=EB=A7=A4=EC=B9=AD=20=EC=9E=AC=EC=A7=84=EC=9E=85=20?= =?UTF-8?q?=EC=8B=9C=20=EB=8C=80=EA=B8=B0=ED=99=94=EB=A9=B4=20=EC=98=A4?= =?UTF-8?q?=EB=85=B8=EC=B6=9C=20=EC=88=98=EC=A0=95=20(resync=EB=8A=94=20Wa?= =?UTF-8?q?iting=EC=97=90=EC=84=9C=EB=A7=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../instructormatching/screen/MatchingScreen.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/screen/MatchingScreen.kt b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/screen/MatchingScreen.kt index bc2b8c61..f612f570 100644 --- a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/screen/MatchingScreen.kt +++ b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/screen/MatchingScreen.kt @@ -9,6 +9,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.ssing.core.ui.common.component.SsingModal import com.ssing.core.ui.designsystem.theme.SSINGTheme @@ -38,6 +40,10 @@ internal fun MatchingRoute( val state by viewModel.uiState.collectAsStateWithLifecycle() val context = LocalContext.current + LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { + viewModel.resyncActiveOffer() + } + HandleUiEffects(viewModel.uiEffect) { effect -> when (effect) { is MatchingContract.Effect.ShowToast -> context.toast(effect.message) From 537aa4951ec946b1120c4f466e7b86f6e2b540e4 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 02:04:54 +0900 Subject: [PATCH 17/19] =?UTF-8?q?[fix/#204]=20=EB=A7=A4=EC=B9=AD=20?= =?UTF-8?q?=EB=8C=80=EA=B8=B0=20=ED=99=94=EB=A9=B4=20=EC=A0=84=ED=99=98=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 확정 전(제안도착/수락대기) 홈 이동 후 카드 재진입 시 대기화면 오노출 수정 (resyncActiveOffer는 Waiting 상태에서만 재조회) - 대기 중지(종료) 시 조건 입력 화면이 아니라 홈으로 이동 --- .../instructormatching/MatchingViewModel.kt | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt index 1dbe4144..94a20f21 100644 --- a/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt +++ b/presentation/instructor-matching/src/main/java/com/ssing/presentation/instructormatching/MatchingViewModel.kt @@ -54,8 +54,6 @@ internal class MatchingViewModel @Inject constructor( route.startFresh -> Unit else -> restoreActiveOffer() } - instructorMatchingRepository.connectSocket() - instructorMatchingRepository.event .onEach { handleMatchingEvent(it) } .launchIn(viewModelScope) @@ -63,6 +61,8 @@ internal class MatchingViewModel @Inject constructor( instructorMatchingRepository.socketState .onEach { handleSocketState(it) } .launchIn(viewModelScope) + + instructorMatchingRepository.connectSocket() } private fun handleMatchingEvent(event: InstructorMatchingEvent) { @@ -97,7 +97,10 @@ internal class MatchingViewModel @Inject constructor( sendEffect(MatchingContract.Effect.ShowToast("연결에 문제가 발생했어요.")) } } - SocketState.Connected -> socketErrorToastShown = false + SocketState.Connected -> { + socketErrorToastShown = false + resyncActiveOffer() + } SocketState.Connecting, SocketState.Disconnected -> Unit } } @@ -186,6 +189,8 @@ internal class MatchingViewModel @Inject constructor( exposure = exposure.copy(isSubmitting = false), ) } + // 노출 시작 직후 서버가 즉시 만든 제안이 있으면 바로 반영 (선대기 레이스 복구) + restoreActiveOffer() } .onFailure { Timber.e(it, "matching-exposure 저장 실패") @@ -214,7 +219,9 @@ internal class MatchingViewModel @Inject constructor( instructorMatchingRepository.cancelMatchingExposure() .onSuccess { isExposed -> Timber.d("즉시노출 중단 응답 isExposed=$isExposed") - updateState { copy(dialog = null, phase = MatchingPhase.SettingExposure) } + // 대기 중지(종료) 시 조건 입력 화면이 아니라 홈으로 나간다. + updateState { copy(dialog = null) } + sendEffect(MatchingContract.Effect.NavigateBack) } .onFailure { Timber.e(it, "즉시노출 중단 실패") @@ -275,6 +282,19 @@ internal class MatchingViewModel @Inject constructor( private var restoreJob: Job? = null + /** + * 소켓 연결/재연결·화면 재진입 시 현재 활성 제안을 REST로 재동기화한다. + * + * '조건에 맞는 요청을 찾는 중'(Waiting) 상태에서만 재조회한다. + * 이미 제안 도착/확정 대기(OfferArrived·PendingConfirm)이거나 조건 설정(SettingExposure) 중이면, + * active 재조회가 그 상태를 대기 화면으로 덮어쓰면 안 되므로 건너뛴다. + * (확정 전 홈 이동 → 카드로 재진입 시 대기화면이 잘못 뜨는 문제 방지) + */ + fun resyncActiveOffer() { + if (uiState.value.phase !is MatchingPhase.Waiting) return + restoreActiveOffer() + } + fun restoreActiveOffer() { restoreJob?.cancel() restoreJob = viewModelScope.launch { From 77ba5986e24b04a030d508bda0b57076139aa794 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 14:55:12 +0900 Subject: [PATCH 18/19] =?UTF-8?q?[mod/#216]=20=EB=A7=A4=EC=B9=AD=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=EB=90=98=EB=A9=B4=20=ED=99=88=EC=97=90=20?= =?UTF-8?q?=EA=B0=95=EC=8A=B5=20=EC=83=81=EC=84=B8=20=EB=B3=B4=EA=B8=B0?= =?UTF-8?q?=EA=B0=80=20=EB=96=A0=EC=95=BC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/instructorhome/InstructorHomeViewModel.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt index 231f3da1..eef5137f 100644 --- a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt +++ b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeViewModel.kt @@ -110,10 +110,9 @@ internal class InstructorHomeViewModel @Inject constructor( WAITING_FOR_INSTRUCTOR, WAITING_FOR_CONFIRMATION, - PAYMENT_PENDING, - IN_PROGRESS -> Status.Matched - - CONFIRMED -> Status.Default + PAYMENT_PENDING -> Status.Matched + CONFIRMED, + IN_PROGRESS -> Status.Default else -> Status.Default } From 4584e707fda2319bad3e86610dfc3ea45f46ac13 Mon Sep 17 00:00:00 2001 From: oilbeaneda <20221444@sungshin.ac.kr> Date: Fri, 17 Jul 2026 15:02:22 +0900 Subject: [PATCH 19/19] =?UTF-8?q?[mod/#216]=20=20navOptions=20=EB=B3=B4?= =?UTF-8?q?=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ssing/instructor/InstructorMainNavHost.kt | 2 ++ .../presentation/instructorhome/InstructorHomeScreen.kt | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt b/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt index b397d81b..90908c4e 100644 --- a/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt +++ b/app/instructor/src/main/java/com/ssing/instructor/InstructorMainNavHost.kt @@ -57,6 +57,7 @@ internal fun InstructorMainNavHost( navigateToMatching = { navController.navigate( route = InstructorMatching(startFresh = true), + navOptions = navController.backToHomeNavOptions(), ) }, navigateToLessonDetail = { lessonId -> @@ -68,6 +69,7 @@ internal fun InstructorMainNavHost( navigateToMatchingWaiting = { offerId -> navController.navigate( route = InstructorMatching(offerId = offerId), + navOptions = navController.backToHomeNavOptions(), ) }, navigateToNotification = { diff --git a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt index 8c265c8b..3832e85c 100644 --- a/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt +++ b/presentation/instructor-home/src/main/java/com/ssing/presentation/instructorhome/InstructorHomeScreen.kt @@ -50,9 +50,9 @@ internal fun InstructorHomeRoute( navigateToLessonDetail: (Long?) -> Unit, navigateToMatching: () -> Unit, navigateToNotification: () -> Unit, + navigateToMatchingWaiting: (Long?) -> Unit, modifier: Modifier = Modifier, viewModel: InstructorHomeViewModel = hiltViewModel(), - navigateToMatchingWaiting: (Long?) -> Unit, ) { val state by viewModel.uiState.collectAsStateWithLifecycle() val context = LocalContext.current @@ -63,7 +63,10 @@ internal fun InstructorHomeRoute( HandleUiEffects(viewModel.uiEffect) { effect -> when (effect) { - is InstructorHomeContract.Effect.NavigateToMatchingWaiting -> navigateToMatchingWaiting(effect.offerId) + is InstructorHomeContract.Effect.NavigateToMatchingWaiting -> navigateToMatchingWaiting( + effect.offerId + ) + is InstructorHomeContract.Effect.NavigateToLessonDetail -> navigateToLessonDetail(effect.lessonId) is InstructorHomeContract.Effect.NavigateToMatching -> navigateToMatching() is InstructorHomeContract.Effect.ShowToast -> context.toast(effect.message)