From 809ed5ce23ee169b27b9107dd79f80250afa2269 Mon Sep 17 00:00:00 2001 From: klaus Date: Wed, 8 Jul 2026 16:04:01 +0900 Subject: [PATCH] =?UTF-8?q?refactor(modal):=20V2=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20=EC=82=AC=EC=9A=A9=EC=B2=98=EB=A5=BC=20?= =?UTF-8?q?=EA=B5=90=EC=B2=B4=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../creator/channel/CreatorChannelActivity.kt | 51 ++++--- .../CreatorChannelFanTalkWriteActivity.kt | 8 +- .../v2/live/onair/HomeOnAirLiveActivity.kt | 8 +- .../sodalive/v2/main/MainV2LoginGuard.kt | 8 +- .../CreatorChannelActivitySourceTest.kt | 10 +- .../CreatorChannelFanTalkWriteSourceTest.kt | 2 + .../v2/main/MainV2LoginGuardSourceTest.kt | 3 +- docs/20260708_V2_공통_모달창/plan-task.md | 137 ++++++++++++++++++ docs/20260708_V2_공통_모달창/prd.md | 93 ++++++++++++ 9 files changed, 281 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt index 70432bd0..52d33bf7 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt @@ -6,14 +6,12 @@ import android.animation.ValueAnimator import android.content.Context import android.content.Intent import android.graphics.Color -import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.View.MeasureSpec import android.view.animation.Interpolator import android.widget.LinearLayout import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AlertDialog import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat @@ -31,7 +29,6 @@ import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity import kr.co.vividnext.sodalive.base.BaseActivity -import kr.co.vividnext.sodalive.base.SodaDialog import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.LoadingDialog @@ -53,9 +50,11 @@ import kr.co.vividnext.sodalive.mypage.MyPageViewModel import kr.co.vividnext.sodalive.mypage.auth.Auth import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse -import kr.co.vividnext.sodalive.report.ProfileReportDialog import kr.co.vividnext.sodalive.report.UserReportDialog import kr.co.vividnext.sodalive.settings.ContentSettingsActivity +import kr.co.vividnext.sodalive.splash.SplashActivity +import kr.co.vividnext.sodalive.user.login.LoginActivity +import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog import kr.co.vividnext.sodalive.v2.common.CreatorActivityType import kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioFragment import kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragment @@ -75,8 +74,6 @@ import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTitleBarS import kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesFragment import kr.co.vividnext.sodalive.v2.main.MainV2Activity import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity -import kr.co.vividnext.sodalive.splash.SplashActivity -import kr.co.vividnext.sodalive.user.login.LoginActivity import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.toRequestBody import org.koin.android.ext.android.inject @@ -383,14 +380,18 @@ class CreatorChannelActivity : private fun showUserBlockDialog() { val nickname = currentHeader?.nickname.orEmpty() - AlertDialog.Builder(this) - .setTitle(getString(R.string.screen_live_room_block_title)) - .setMessage(getString(R.string.screen_live_room_block_message_user, nickname)) - .setPositiveButton(getString(R.string.screen_live_room_block_confirm)) { _, _ -> + V2ModalDialog( + activity = this, + layoutInflater = layoutInflater, + title = getString(R.string.screen_live_room_block_title), + desc = getString(R.string.screen_live_room_block_message_user, nickname), + confirmButtonTitle = getString(R.string.screen_live_room_block_confirm), + confirmButtonClick = { homeActionDelegate?.blockUser() - } - .setNegativeButton(getString(R.string.cancel)) { _, _ -> } - .show() + }, + cancelButtonTitle = getString(R.string.cancel), + cancelButtonClick = {} + ).show(screenWidth) } private fun showUserReportDialog() { @@ -400,9 +401,18 @@ class CreatorChannelActivity : } private fun showProfileReportDialog() { - ProfileReportDialog(this, layoutInflater) { - homeActionDelegate?.reportProfile() - }.show(resources.displayMetrics.widthPixels) + V2ModalDialog( + activity = this, + layoutInflater = layoutInflater, + title = getString(R.string.dialog_profile_report_title), + desc = getString(R.string.dialog_profile_report_notice), + confirmButtonTitle = getString(R.string.report_button), + confirmButtonClick = { + homeActionDelegate?.reportProfile() + }, + cancelButtonTitle = getString(R.string.cancel), + cancelButtonClick = {} + ).show(resources.displayMetrics.widthPixels) } private fun setupTabsAndPager() { @@ -604,7 +614,7 @@ class CreatorChannelActivity : } override fun onCreatorChannelFanTalkDeleteClicked(fanTalkId: Long) { - SodaDialog( + V2ModalDialog( activity = this, layoutInflater = layoutInflater, title = getString(R.string.screen_user_profile_cheer_delete_title), @@ -640,7 +650,7 @@ class CreatorChannelActivity : } private fun showCreatorChannelCommunityDeleteDialog(item: CreatorChannelCommunityPostUiModel) { - SodaDialog( + V2ModalDialog( activity = this, layoutInflater = layoutInflater, title = getString(R.string.screen_creator_community_delete_title), @@ -977,7 +987,7 @@ class CreatorChannelActivity : if (isAdult) { val isKoreanCountry = SharedPreferenceManager.countryCode.ifBlank { "KR" } == "KR" if (isKoreanCountry && !SharedPreferenceManager.isAuth) { - SodaDialog( + V2ModalDialog( activity = this, layoutInflater = layoutInflater, title = getString(R.string.auth_title), @@ -985,8 +995,7 @@ class CreatorChannelActivity : confirmButtonTitle = getString(R.string.auth_go), confirmButtonClick = { startAuthFlow() }, cancelButtonTitle = getString(R.string.cancel), - cancelButtonClick = {}, - descGravity = Gravity.CENTER + cancelButtonClick = {} ).show(screenWidth) return } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteActivity.kt index 4020d40d..695ccb74 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteActivity.kt @@ -3,7 +3,6 @@ package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write import android.content.Context import android.content.Intent import android.text.InputFilter -import android.view.Gravity import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.activity.addCallback @@ -13,8 +12,8 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.widget.addTextChangedListener import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.base.BaseActivity -import kr.co.vividnext.sodalive.base.SodaDialog import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelFantalkWriteBinding +import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog import org.koin.androidx.viewmodel.ext.android.viewModel class CreatorChannelFanTalkWriteActivity : BaseActivity( @@ -121,7 +120,7 @@ class CreatorChannelFanTalkWriteActivity : BaseActivity( if (isAdult) { val isKoreanCountry = SharedPreferenceManager.countryCode.ifBlank { "KR" } == "KR" if (isKoreanCountry && !SharedPreferenceManager.isAuth) { - SodaDialog( + V2ModalDialog( activity = this, layoutInflater = layoutInflater, title = getString(R.string.auth_title), @@ -191,8 +190,7 @@ class HomeOnAirLiveActivity : BaseActivity( confirmButtonTitle = getString(R.string.auth_go), confirmButtonClick = { startAuthFlow() }, cancelButtonTitle = getString(R.string.cancel), - cancelButtonClick = {}, - descGravity = Gravity.CENTER + cancelButtonClick = {} ).show(screenWidth) return } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuard.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuard.kt index 76b3e075..e6c9748d 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuard.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuard.kt @@ -1,11 +1,9 @@ package kr.co.vividnext.sodalive.v2.main import android.content.Intent -import android.view.Gravity import androidx.fragment.app.Fragment import com.google.gson.Gson import kr.co.vividnext.sodalive.R -import kr.co.vividnext.sodalive.base.SodaDialog import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.mypage.MyPageViewModel @@ -14,6 +12,7 @@ import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse import kr.co.vividnext.sodalive.settings.ContentSettingsActivity import kr.co.vividnext.sodalive.splash.SplashActivity +import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog import org.koin.android.ext.android.get fun Fragment.ensureMainV2NavigationAllowed( @@ -46,7 +45,7 @@ fun Fragment.ensureMainV2NavigationAllowed( } private fun Fragment.showMainV2AuthDialog() { - SodaDialog( + V2ModalDialog( activity = requireActivity(), layoutInflater = layoutInflater, title = getString(R.string.auth_title), @@ -54,8 +53,7 @@ private fun Fragment.showMainV2AuthDialog() { confirmButtonTitle = getString(R.string.auth_go), confirmButtonClick = { startMainV2AuthFlow() }, cancelButtonTitle = getString(R.string.cancel), - cancelButtonClick = {}, - descGravity = Gravity.CENTER + cancelButtonClick = {} ).show(resources.displayMetrics.widthPixels) } diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt index 0b65c31a..c532b05e 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt @@ -85,6 +85,10 @@ class CreatorChannelActivitySourceTest { assertTrue(source.contains("showUserReportDialog")) assertTrue(source.contains("showProfileReportDialog")) assertTrue(source.contains("homeActionDelegate?.blockUser")) + assertTrue(source.contains("V2ModalDialog(")) + assertFalse(source.contains("AlertDialog.Builder(")) + assertFalse(source.contains("import kr.co.vividnext.sodalive.report.ProfileReportDialog")) + assertFalse(source.contains("ProfileReportDialog(this, layoutInflater)")) assertFalse(source.contains("creator_channel_more_ready")) assertTrue(sheet.contains("BottomSheetDialogFragment")) assertTrue(sheet.contains("onClickBlock")) @@ -599,7 +603,8 @@ class CreatorChannelActivitySourceTest { assertTrue(source.contains("position == CreatorChannelTab.FanTalk.ordinal")) assertTrue(source.contains("override fun onCreatorChannelFanTalkContentChanged()")) assertTrue(source.contains("override fun onCreatorChannelFanTalkDeleteClicked(fanTalkId: Long)")) - assertTrue(source.contains("SodaDialog(")) + assertTrue(source.contains("V2ModalDialog(")) + assertFalse(source.contains("SodaDialog(")) assertTrue(source.contains("R.string.screen_user_profile_cheer_delete_title")) assertTrue(source.contains("R.string.screen_user_profile_cheer_delete_desc")) assertTrue(source.contains("R.string.confirm_delete_title")) @@ -1371,7 +1376,8 @@ class CreatorChannelActivitySourceTest { assertTrue(activity.contains("liveCoordinator.enterLiveRoom(live.liveId)")) assertTrue(activity.contains("private fun ensureLoginAndAdultAuth(isAdult: Boolean, onAuthed: () -> Unit)")) assertTrue(activity.contains("SharedPreferenceManager.token.isBlank()")) - assertTrue(activity.contains("SodaDialog(")) + assertTrue(activity.contains("V2ModalDialog(")) + assertFalse(activity.contains("SodaDialog(")) assertTrue(activity.contains("R.string.auth_desc_live")) assertTrue(activity.contains("ContentSettingsActivity::class.java")) assertTrue(activity.contains("Constants.EXTRA_SHOW_SENSITIVE_CONTENT_GUIDE")) diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteSourceTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteSourceTest.kt index 9ad005f3..144f6267 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteSourceTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteSourceTest.kt @@ -23,6 +23,8 @@ class CreatorChannelFanTalkWriteSourceTest { assertTrue(source.contains("WindowInsetsCompat.Type.ime()")) assertTrue(source.contains("onBackPressedDispatcher.addCallback")) assertTrue(source.contains("showCancelConfirmDialog()")) + assertTrue(source.contains("V2ModalDialog(")) + assertFalse(source.contains("SodaDialog(")) assertTrue(source.contains("setResult(RESULT_OK)")) assertTrue(source.contains("viewModel.submit(creatorId)")) assertTrue(manifest.contains(".v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteActivity")) diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuardSourceTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuardSourceTest.kt index 24ab461f..fee1a593 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuardSourceTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuardSourceTest.kt @@ -17,7 +17,8 @@ class MainV2LoginGuardSourceTest { assertTrue(source.contains("(activity as? MainV2Activity)?.showLoginActivity()")) assertTrue(source.contains("SharedPreferenceManager.countryCode.ifBlank { \"KR\" }")) assertTrue(source.contains("SharedPreferenceManager.isAuth")) - assertTrue(source.contains("SodaDialog(")) + assertTrue(source.contains("V2ModalDialog(")) + assertFalse(source.contains("SodaDialog(")) assertTrue(source.contains("Auth.auth(requireActivity(), requireContext())")) assertTrue(source.contains("Gson().fromJson(json, BootpayResponse::class.java)")) assertTrue(source.contains("AuthVerifyRequest(receiptId = bootpayResponse.data.receiptId)")) diff --git a/docs/20260708_V2_공통_모달창/plan-task.md b/docs/20260708_V2_공통_모달창/plan-task.md index 994271e2..811a5446 100644 --- a/docs/20260708_V2_공통_모달창/plan-task.md +++ b/docs/20260708_V2_공통_모달창/plan-task.md @@ -135,6 +135,142 @@ --- +### Phase 4: V2 사용처 `dialog_v2_modal` 적용 대상 조사 + +- [x] **Task 4.1: `kr.co.vividnext.sodalive.v2` 하위 dialog/modal 사용처 검색** + - 확인: + - `app/src/main/java/kr/co/vividnext/sodalive/v2` + - `app/src/test/java/kr/co/vividnext/sodalive/v2` + - 검색 키워드: + - `SodaDialog` + - `AlertDialog.Builder` + - `V2ModalDialog` + - `DialogFragment` + - `BottomSheetDialogFragment` + - `PopupWindow` + - `LivePaymentDialog` + - `LiveRoomPasswordDialog` + - `LiveCancelDialog` + - `LiveRoomDonationDialog` + - `UserReportDialog` + - `ProfileReportDialog` + - `CheersReportDialog` + - `NotificationSettingsDialog` + - `EventPopupDialogFragment` + - 검증 명령: + - `rg -n "SodaDialog|V2ModalDialog|AlertDialog\\.Builder|AlertDialog" app/src/main/java/kr/co/vividnext/sodalive/v2 app/src/test/java/kr/co/vividnext/sodalive/v2` + - `rg -n "SodaDialog\\(|AlertDialog\\.Builder\\(|UserReportDialog\\(|ProfileReportDialog\\(|CheersReportDialog\\(|LivePaymentDialog\\(|LiveRoomPasswordDialog\\(|LiveCancelDialog\\(|LiveRoomDonationDialog\\(|NotificationSettingsDialog\\(|EventPopupDialogFragment\\(|BottomSheetDialogFragment|PopupWindow" app/src/main/java/kr/co/vividnext/sodalive/v2` + - 기대 결과: + - 현 `V2ModalDialog`로 직접 바꿀 수 있는 확인형 모달과 별도 설계가 필요한 커스텀 다이얼로그를 구분한다. + - 검증 기록: + - 2026-07-08 실행 결과, 직접 교체 후보는 `SodaDialog` 6곳, `AlertDialog.Builder` 1곳, `ProfileReportDialog` 1곳으로 분류했다. + - 입력/라디오/이미지/토글/BottomSheet/PopupWindow 기반 사용처는 현 `V2ModalDialog` API로 직접 표현하기 어려워 별도 설계 후보로 분류했다. + +- [x] **Task 4.2: 교체 후보와 테스트 영향 문서화** + - 수정: + - `docs/20260708_V2_공통_모달창/prd.md` + - `docs/20260708_V2_공통_모달창/plan-task.md` + - 바로 교체 가능한 후보: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuard.kt:49` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt:186` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt:386` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt:403` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt:607` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt:643` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt:980` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteActivity.kt:124` + - 테스트 영향: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuardSourceTest.kt:20` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt:602` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt:1374` + - 검증 명령: + - `rg -n "SodaDialog\\(|AlertDialog\\.Builder\\(|ProfileReportDialog\\(" app/src/main/java/kr/co/vividnext/sodalive/v2 app/src/test/java/kr/co/vividnext/sodalive/v2` + - 기대 결과: + - 후속 구현자가 교체 파일, 테스트 파일, 제외/보류 대상을 문서만으로 파악할 수 있다. + - 검증 기록: + - 2026-07-08 문서 작성 단계에서는 구현 변경이 없으므로 Gradle 검증을 실행하지 않았다. + +--- + +### Phase 5: 확인형 모달 `V2ModalDialog` 교체 구현 + +- [x] **Task 5.1: `SodaDialog`, `AlertDialog.Builder`, `ProfileReportDialog` 직접 교체 후보를 `V2ModalDialog`로 변경** + - 수정: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuard.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteActivity.kt` + - 작업: + - `kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog`를 import한다. + - 확인/취소형 모달의 title, desc, confirm/cancel title, confirm/cancel action을 기존 동작과 동일하게 매핑한다. + - `descGravity = Gravity.CENTER`는 현 `dialog_v2_modal` desc가 중앙 정렬이므로 제거한다. + - 교체 후 미사용 `SodaDialog`, `AlertDialog`, `Gravity`, `ProfileReportDialog` import를 제거한다. + - 레거시 다이얼로그 클래스와 레거시 layout은 수정하지 않는다. + - 검증 명령: + - `./gradlew :app:compileDebugKotlin` + - `git diff --check` + - 기대 결과: + - 직접 교체 후보가 `dialog_v2_modal` 기반으로 표시되고 컴파일 오류가 없다. + - 검증 기록: + - 2026-07-08 `MainV2LoginGuard.kt`, `HomeOnAirLiveActivity.kt`, `CreatorChannelActivity.kt`, `CreatorChannelFanTalkWriteActivity.kt`의 직접 교체 후보를 `V2ModalDialog`로 변경했다. `descGravity = Gravity.CENTER`는 `dialog_v2_modal`의 desc 중앙 정렬 계약으로 대체되어 제거했고, 기존 title/desc/button string과 confirm/cancel callback은 유지했다. + - 2026-07-08 `rg -n "SodaDialog\(|AlertDialog\.Builder\(|ProfileReportDialog\(|descGravity|import android.view.Gravity|import kr.co.vividnext.sodalive.base.SodaDialog|import androidx.appcompat.app.AlertDialog|import kr.co.vividnext.sodalive.report.ProfileReportDialog" ...` 확인 결과, 생산 코드의 직접 교체 대상 레거시 생성자/import는 남지 않았다. 검색에 남은 `showProfileReportDialog`는 V2 모달을 여는 메서드명이다. + +- [x] **Task 5.2: source 테스트 기대값 갱신** + - 수정: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuardSourceTest.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt` + - 작업: + - `SodaDialog(` 문자열 기대를 `V2ModalDialog(` 또는 더 구체적인 V2 모달 import/호출 계약으로 바꾼다. + - `CreatorChannelActivity`의 사용자 차단/프로필 신고/삭제/성인 인증 확인 흐름이 기존 callback을 유지하는지 source 계약으로 확인한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2LoginGuardSourceTest"` + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"` + - 기대 결과: + - 갱신된 source 테스트가 PASS한다. + - 검증 기록: + - 2026-07-08 `MainV2LoginGuardSourceTest`, `CreatorChannelActivitySourceTest`, `CreatorChannelFanTalkWriteSourceTest`의 source 계약을 `V2ModalDialog(` 기대와 레거시 `SodaDialog(`/`AlertDialog.Builder(`/`ProfileReportDialog(this, layoutInflater)` 금지 조건으로 갱신했다. + - 2026-07-08 테스트 변경 직후 병렬 Gradle 실행은 Kotlin incremental cache 경합으로 test assertion 전에 실패했다. 이후 `--no-parallel --no-daemon -Pkotlin.incremental=false`로 직렬 실행해 `MainV2LoginGuardSourceTest`, `CreatorChannelActivitySourceTest`, `CreatorChannelFanTalkWriteSourceTest`가 PASS함을 확인했다. + +- [x] **Task 5.3: 직접 교체 범위 회귀 검증** + - 실행: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.components.modal.*"` + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2LoginGuardSourceTest"` + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"` + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteSourceTest"` + - `./gradlew :app:compileDebugKotlin` + - `git diff --check` + - 기대 결과: + - 직접 교체 범위 테스트와 컴파일이 PASS한다. + - 검증 기록: + - 2026-07-08 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.components.modal.*" --no-parallel --no-daemon -Pkotlin.incremental=false` PASS. + - 2026-07-08 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2LoginGuardSourceTest" --no-parallel --no-daemon -Pkotlin.incremental=false` PASS. + - 2026-07-08 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --no-parallel --no-daemon -Pkotlin.incremental=false` PASS. + - 2026-07-08 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteSourceTest" --no-parallel --no-daemon -Pkotlin.incremental=false` PASS. + - 2026-07-08 `./gradlew :app:compileDebugKotlin --no-parallel --no-daemon -Pkotlin.incremental=false` PASS. + - 2026-07-08 `git diff --check` PASS. + +--- + +### Phase 6: 커스텀 콘텐츠 다이얼로그 후속 설계 + +- [ ] **Task 6.1: 현 `V2ModalDialog`로 직접 표현할 수 없는 사용처 처리 방침 결정** + - 확인 대상: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelLiveCoordinator.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelMoreBottomSheet.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelSortPopup.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkMorePopup.kt` + - 결정 필요: + - report 라디오 목록, password/reason 입력, payment 시간 안내, donation BottomSheet, notification toggle, event image popup을 `dialog_v2_modal` 기반으로 새로 설계할지 여부 + - BottomSheet/PopupWindow 메뉴를 모달 교체 범위에서 제외할지 여부 + - 검증 기준: + - 결정 결과를 이 문서의 Phase 6 아래에 추가하고, 구현 전 PRD를 먼저 갱신한다. + +--- + ## Verification Log - 2026-07-08 문서 작성 단계: 구현 전이므로 Gradle 검증을 실행하지 않았다. Figma `567:17662`, `567:17666` 디자인 컨텍스트와 스크린샷으로 layout 구조/색상 값을 확정했고, `v2/components/modal` 빈 패키지와 `gray_900`/`soda_400` 색상 리소스 존재를 확인했다. @@ -144,3 +280,4 @@ - 2026-07-08 최종 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.components.modal.*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `git diff --check` PASS. `./gradlew :app:ktlintCheck`는 기존 레거시/타 영역 파일의 package-name, trailing comma, indentation 등 사전 존재 위반으로 FAIL했으며, ktlint 보고서에 이번 변경 파일(`V2ModalDialog.kt`, `V2ModalDialogTest.kt`, `dialog_v2_modal.xml`, `bg_v2_modal.xml`)은 포함되지 않았다. Figma `567:17662`, `567:17666` 스크린샷 기준으로 컨테이너/타이틀/본문/버튼 영역, 버튼 1:1 구성, `gray_900`/white/`soda_400` 색상을 대조했다. - 2026-07-08 리뷰 반영: 투명 `bg_v2_modal_button.xml`은 실제 시각 효과가 없어 제거했다. 버튼 중복 클릭 시 action이 여러 번 실행될 수 있는 위험을 막기 위해 최초 클릭 1회만 dismiss/action을 수행하도록 보완했고, 긴 title 줄바꿈 시 잘림을 줄이기 위해 title 높이를 `wrap_content` + `minHeight=64dp`로 조정했다. - 2026-07-08 코드 리뷰/검증: `V2ModalDialog.kt`, `dialog_v2_modal.xml`, `bg_v2_modal.xml`, `V2ModalDialogTest.kt`를 PRD와 Figma MCP `567:17662`, `567:17666` 기준으로 재대조했으며 차단 이슈는 발견하지 않았다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.components.modal.*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `git diff --check` PASS. 신규 untracked 파일은 `git diff --no-index --check /dev/null `로 별도 확인했고 whitespace 오류 출력이 없었다. `./gradlew :app:ktlintCheck`는 기존 레거시/타 영역 위반으로 FAIL했으며 ktlint 보고서에서 이번 변경 파일명 검색 결과는 없었다. +- 2026-07-08 Phase 5 구현: `MainV2LoginGuard.kt`, `HomeOnAirLiveActivity.kt`, `CreatorChannelActivity.kt`, `CreatorChannelFanTalkWriteActivity.kt`의 직접 교체 후보를 `V2ModalDialog`로 변경하고 source test 계약을 갱신했다. 병렬 RED 실행은 Kotlin incremental cache 경합으로 assertion 전 실패했으나, 이후 비증분/직렬 실행으로 `MainV2LoginGuardSourceTest`, `CreatorChannelActivitySourceTest`, `CreatorChannelFanTalkWriteSourceTest`, `v2.components.modal.*` 테스트가 PASS했다. `./gradlew :app:compileDebugKotlin --no-parallel --no-daemon -Pkotlin.incremental=false`와 `git diff --check`도 PASS했다. diff --git a/docs/20260708_V2_공통_모달창/prd.md b/docs/20260708_V2_공통_모달창/prd.md index 3f2cdd06..3e1b042f 100644 --- a/docs/20260708_V2_공통_모달창/prd.md +++ b/docs/20260708_V2_공통_모달창/prd.md @@ -110,6 +110,99 @@ Figma 디자인 그대로 layout을 구성한다. --- +## 12. Addendum: V2 사용처 `dialog_v2_modal` 적용 조사 +2026-07-08 후속 요청 범위는 구현 없이 `kr.co.vividnext.sodalive.v2` 하위에서 modal/dialog 계열을 사용하는 위치를 찾고, 이후 `dialog_v2_modal` 기반 `V2ModalDialog`로 바꿀 대상을 문서화하는 것이다. + +### 추가 Problem +- `V2ModalDialog`와 `dialog_v2_modal.xml`은 구현되어 있지만, V2 화면 일부는 여전히 레거시 `SodaDialog`, 기본 `AlertDialog.Builder`, 레거시 report/payment/password dialog를 호출한다. +- V2 화면의 확인/취소형 모달이 서로 다른 layout과 스타일을 사용하면 V2 디자인 일관성이 깨진다. +- 레거시 다이얼로그 클래스 자체는 다른 레거시 화면에서도 사용되므로, 저장소 원칙상 레거시 파일을 직접 수정하지 않고 V2 호출부에서 대체해야 한다. + +### 추가 Goals +- V2 패키지 안의 확인/취소형 모달은 `V2ModalDialog`를 사용해 `dialog_v2_modal` 디자인을 적용한다. +- title/desc/confirm/cancel만 필요한 사용처는 우선 교체 대상으로 삼는다. +- 입력 필드, 라디오 목록, 이미지, 토글, BottomSheet, PopupWindow 등 `V2ModalDialog`의 현재 API로 표현할 수 없는 UI는 직접 교체하지 않고 별도 설계 대상으로 분리한다. +- 후속 구현 시 기존 문자열, confirm/cancel 콜백, `show(width)` 폭 계산, dismiss 후 action 실행 흐름을 유지한다. + +### 추가 Non-Goals +- 이번 조사 단계에서는 Kotlin/XML 구현을 변경하지 않는다. +- `kr.co.vividnext.sodalive.base.SodaDialog`, `kr.co.vividnext.sodalive.report.*Dialog`, `kr.co.vividnext.sodalive.live.room.*Dialog` 같은 레거시 공용 클래스를 직접 수정하지 않는다. +- `LoadingDialog`, `Toast`, 정렬/더보기용 `PopupWindow`, 메뉴성 BottomSheet는 확인형 모달 교체 범위에서 제외한다. +- 입력/라디오/이미지/토글 UI를 `V2ModalDialog`에 무리하게 넣기 위한 API 확장은 이번 문서 단계에서 결정하지 않는다. + +### 바로 교체 가능한 후보 +아래 항목은 title/desc/확인/취소 콜백 구조라 현 `V2ModalDialog`로 직접 교체할 수 있다. + +- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuard.kt:49` + - 현재: `SodaDialog` + - 용도: 성인 인증 필요 안내 + - 후속 변경: `V2ModalDialog`로 교체하고 `Gravity` import 제거 확인 +- `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt:186` + - 현재: `SodaDialog` + - 용도: 성인 인증 필요 안내 + - 후속 변경: `V2ModalDialog`로 교체하고 `Gravity` import 제거 확인 +- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt:386` + - 현재: `AlertDialog.Builder` + - 용도: 사용자 차단 확인 + - 후속 변경: `setTitle`/`setMessage`/positive/negative 버튼을 `V2ModalDialog`의 title/desc/confirm/cancel로 매핑 +- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt:403` + - 현재: `ProfileReportDialog` + - 용도: 프로필 사진 신고 확인 + - 후속 변경: `dialog_profile_report_title`, `dialog_profile_report_notice`, `report_button`, `cancel`을 `V2ModalDialog`로 매핑 +- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt:607` + - 현재: `SodaDialog` + - 용도: FanTalk 삭제 확인 + - 후속 변경: 기존 삭제 confirm 콜백 유지 +- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt:643` + - 현재: `SodaDialog` + - 용도: 커뮤니티 게시글 삭제 확인 + - 후속 변경: 기존 삭제 confirm 콜백 유지 +- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt:980` + - 현재: `SodaDialog` + - 용도: 성인 인증 필요 안내 + - 후속 변경: `V2ModalDialog`로 교체하고 `Gravity` import 제거 확인 +- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteActivity.kt:124` + - 현재: `SodaDialog` + - 용도: 글 작성 취소/이탈 확인 + - 후속 변경: `V2ModalDialog`로 교체하고 `Gravity` import 제거 확인 + +### 추가 설계가 필요한 후보 +아래 항목은 dialog/modal 계열이지만 현 `V2ModalDialog` API가 제공하지 않는 입력, 라디오, 이미지, 토글, BottomSheet 동작을 포함한다. `dialog_v2_modal` 적용 여부는 별도 V2 전용 컴포넌트 또는 `V2ModalDialog` API 확장 결정을 먼저 해야 한다. + +- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt:162`, `:530` + - `NotificationSettingsDialog`: 알림 토글 3개가 필요하다. + - `EventPopupDialogFragment`: 이미지형 이벤트 BottomSheet다. +- `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt:243`, `:280` + - `LiveRoomPasswordDialog`: 비밀번호 입력과 금액 표시가 필요하다. + - `LivePaymentDialog`: 유료 라이브 결제 확인이며, 시작/현재 시각 안내 영역이 조건부로 표시된다. +- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt:397`, `:898` + - `UserReportDialog`: 신고 사유 `RadioGroup` 선택값이 필요하다. + - `LiveRoomDonationDialog`: 후원 금액/메시지/비밀 여부/충전 이동이 필요한 BottomSheet다. +- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt:162` + - `CheersReportDialog`: 신고 사유 `RadioGroup` 선택값이 필요하다. +- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelLiveCoordinator.kt:101`, `:142`, `:151`, `:195`, `:235`, `:250` + - `LiveRoomPasswordDialog`: 비밀번호 입력이 필요하다. + - `LivePaymentDialog`: 결제 확인과 조건부 시간 안내가 필요하다. + - `LiveCancelDialog`: 취소 사유 입력이 필요하다. +- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelMoreBottomSheet.kt` + - `BottomSheetDialogFragment` 기반 더보기 메뉴다. +- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelSortPopup.kt` + - `PopupWindow` 기반 정렬 메뉴다. +- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkMorePopup.kt` + - `PopupWindow` 기반 FanTalk 더보기 메뉴다. + +### 테스트 영향 +- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuardSourceTest.kt:20`은 현재 `SodaDialog(` 문자열을 기대하므로 후속 구현 시 `V2ModalDialog(` 기대값으로 갱신해야 한다. +- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt:602`, `:1374`는 현재 `SodaDialog(` 문자열을 기대하므로 후속 구현 시 `V2ModalDialog(` 또는 더 구체적인 V2 모달 계약으로 갱신해야 한다. +- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteSourceTest.kt`는 `showCancelConfirmDialog()` 흐름만 확인하므로 직접 문자열 기대 변경은 필요하지 않을 가능성이 높다. + +### 추가 Open Questions +- `dialog_v2_modal` 적용 범위를 현 `V2ModalDialog`가 표현 가능한 확인/취소형 모달로 제한할지, report/password/payment/donation 같은 커스텀 콘텐츠 다이얼로그까지 V2 전용 컴포넌트로 확장할지 결정이 필요하다. +- `LivePaymentDialog`의 시작/현재 시각 안내 영역을 V2 모달 디자인 안에서 보존해야 하는지, 단순 desc로 축약해도 되는지 결정이 필요하다. +- BottomSheet와 PopupWindow 메뉴는 `dialog_v2_modal` 적용 대상에서 제외하는 것이 자연스럽지만, 디자인 정책상 V2 전용 메뉴 컴포넌트가 필요한지 별도 확인이 필요하다. + +--- + ## Figma 참조 - 버튼 1개: `https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=567-17662&m=dev` - 버튼 2개: `https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=567-17666&m=dev`