From d96001e213931e1d8053addb2a641e30be207451 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Wed, 8 Jul 2026 19:02:14 +0900 Subject: [PATCH] =?UTF-8?q?refactor(v2):=20=EC=95=A1=EC=85=98=20=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=EB=A1=9C=20=EC=A0=84=ED=99=98=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SodaLive.xcodeproj/project.pbxproj | 132 ++++---- .../CreatorChannel/CreatorChannelView.swift | 62 ++-- SodaLive/Sources/V2/Main/MainView.swift | 49 +-- .../plan-task.md | 295 ++++++++++++++++++ 4 files changed, 428 insertions(+), 110 deletions(-) create mode 100644 docs/20260708_V2_ActionModal_전환_조사/plan-task.md diff --git a/SodaLive.xcodeproj/project.pbxproj b/SodaLive.xcodeproj/project.pbxproj index 112eb5f0..efdd4e3b 100644 --- a/SodaLive.xcodeproj/project.pbxproj +++ b/SodaLive.xcodeproj/project.pbxproj @@ -1804,6 +1804,27 @@ C0A733000000000000000003 /* ContentOverviewRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A730000000000000000003 /* ContentOverviewRepository.swift */; }; C0A733000000000000000004 /* ContentOverviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A730000000000000000004 /* ContentOverviewViewModel.swift */; }; C0A733000000000000000005 /* ContentOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A730000000000000000005 /* ContentOverviewView.swift */; }; + C0A741000000000000000001 /* MainChatRoomsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000001 /* MainChatRoomsResponse.swift */; }; + C0A741000000000000000002 /* MainChatApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000002 /* MainChatApi.swift */; }; + C0A741000000000000000003 /* MainChatRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000003 /* MainChatRepository.swift */; }; + C0A741000000000000000004 /* MainChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000004 /* MainChatViewModel.swift */; }; + C0A741000000000000000005 /* MainChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000005 /* MainChatView.swift */; }; + C0A741000000000000000006 /* MainChatRoomListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000006 /* MainChatRoomListItem.swift */; }; + C0A741000000000000000007 /* MainChatFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000007 /* MainChatFilter.swift */; }; + C0A742000000000000000001 /* MainChatRoomsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000001 /* MainChatRoomsResponse.swift */; }; + C0A742000000000000000002 /* MainChatApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000002 /* MainChatApi.swift */; }; + C0A742000000000000000003 /* MainChatRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000003 /* MainChatRepository.swift */; }; + C0A742000000000000000004 /* MainChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000004 /* MainChatViewModel.swift */; }; + C0A742000000000000000005 /* MainChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000005 /* MainChatView.swift */; }; + C0A742000000000000000006 /* MainChatRoomListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000006 /* MainChatRoomListItem.swift */; }; + C0A742000000000000000007 /* MainChatFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000007 /* MainChatFilter.swift */; }; + C0A743000000000000000001 /* MainChatRoomsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000001 /* MainChatRoomsResponse.swift */; }; + C0A743000000000000000002 /* MainChatApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000002 /* MainChatApi.swift */; }; + C0A743000000000000000003 /* MainChatRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000003 /* MainChatRepository.swift */; }; + C0A743000000000000000004 /* MainChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000004 /* MainChatViewModel.swift */; }; + C0A743000000000000000005 /* MainChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000005 /* MainChatView.swift */; }; + C0A743000000000000000006 /* MainChatRoomListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000006 /* MainChatRoomListItem.swift */; }; + C0A743000000000000000007 /* MainChatFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000007 /* MainChatFilter.swift */; }; C0D6E325AEFEEF476E7044AB /* CommunityPostCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE82912F2F111593BE42E33C /* CommunityPostCard.swift */; }; C3CFF5936F1BAC5BC12E9DDB /* UpdateContentPreferenceRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDFAA64C2CA4FCBBF00A024 /* UpdateContentPreferenceRequest.swift */; }; C45F071870A4DB48ABAE8BC5 /* YandexAdSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F064EC9D5711B91979F45C05 /* YandexAdSupport.swift */; }; @@ -2091,27 +2112,6 @@ FE286EDC3151C5B7D62E5445 /* V2vApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613FD451377C66254773CF47 /* V2vApi.swift */; }; FE3244627FFAD8667541CE01 /* ChannelDonationAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872F4F3506F36E0EAAE312ED /* ChannelDonationAllView.swift */; }; FE3C5D4BCD99FE5D5B389F03 /* GetCreatorDetailResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5D253179845EF7577A1E01 /* GetCreatorDetailResponse.swift */; }; - C0A741000000000000000001 /* MainChatRoomsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000001 /* MainChatRoomsResponse.swift */; }; - C0A742000000000000000001 /* MainChatRoomsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000001 /* MainChatRoomsResponse.swift */; }; - C0A743000000000000000001 /* MainChatRoomsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000001 /* MainChatRoomsResponse.swift */; }; - C0A741000000000000000002 /* MainChatApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000002 /* MainChatApi.swift */; }; - C0A742000000000000000002 /* MainChatApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000002 /* MainChatApi.swift */; }; - C0A743000000000000000002 /* MainChatApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000002 /* MainChatApi.swift */; }; - C0A741000000000000000003 /* MainChatRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000003 /* MainChatRepository.swift */; }; - C0A742000000000000000003 /* MainChatRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000003 /* MainChatRepository.swift */; }; - C0A743000000000000000003 /* MainChatRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000003 /* MainChatRepository.swift */; }; - C0A741000000000000000004 /* MainChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000004 /* MainChatViewModel.swift */; }; - C0A742000000000000000004 /* MainChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000004 /* MainChatViewModel.swift */; }; - C0A743000000000000000004 /* MainChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000004 /* MainChatViewModel.swift */; }; - C0A741000000000000000005 /* MainChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000005 /* MainChatView.swift */; }; - C0A742000000000000000005 /* MainChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000005 /* MainChatView.swift */; }; - C0A743000000000000000005 /* MainChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000005 /* MainChatView.swift */; }; - C0A741000000000000000006 /* MainChatRoomListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000006 /* MainChatRoomListItem.swift */; }; - C0A742000000000000000006 /* MainChatRoomListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000006 /* MainChatRoomListItem.swift */; }; - C0A743000000000000000006 /* MainChatRoomListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000006 /* MainChatRoomListItem.swift */; }; - C0A741000000000000000007 /* MainChatFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000007 /* MainChatFilter.swift */; }; - C0A742000000000000000007 /* MainChatFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000007 /* MainChatFilter.swift */; }; - C0A743000000000000000007 /* MainChatFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A740000000000000000007 /* MainChatFilter.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -2968,6 +2968,13 @@ C0A730000000000000000003 /* ContentOverviewRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentOverviewRepository.swift; sourceTree = ""; }; C0A730000000000000000004 /* ContentOverviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentOverviewViewModel.swift; sourceTree = ""; }; C0A730000000000000000005 /* ContentOverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentOverviewView.swift; sourceTree = ""; }; + C0A740000000000000000001 /* MainChatRoomsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatRoomsResponse.swift; sourceTree = ""; }; + C0A740000000000000000002 /* MainChatApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatApi.swift; sourceTree = ""; }; + C0A740000000000000000003 /* MainChatRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatRepository.swift; sourceTree = ""; }; + C0A740000000000000000004 /* MainChatViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatViewModel.swift; sourceTree = ""; }; + C0A740000000000000000005 /* MainChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatView.swift; sourceTree = ""; }; + C0A740000000000000000006 /* MainChatRoomListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatRoomListItem.swift; sourceTree = ""; }; + C0A740000000000000000007 /* MainChatFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatFilter.swift; sourceTree = ""; }; C1165E3F4CA1AB019FC85067 /* LanguageRepository.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LanguageRepository.swift; sourceTree = ""; }; C40163E3E9DB325EA984D3C4 /* CreatorChannelFloatingIconButton.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CreatorChannelFloatingIconButton.swift; sourceTree = ""; }; C6B18C00DE256DCC53030630 /* Spacing.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Spacing.swift; sourceTree = ""; }; @@ -3076,13 +3083,6 @@ F26CB443DA0ED4C45A18B258 /* PushNotificationApi.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PushNotificationApi.swift; sourceTree = ""; }; F6966E927CDB1DE31F936757 /* CreatorProfileGrid.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CreatorProfileGrid.swift; sourceTree = ""; }; F7F1421DBAB56E2F6891625E /* DefaultTitleBar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DefaultTitleBar.swift; sourceTree = ""; }; - C0A740000000000000000001 /* MainChatRoomsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatRoomsResponse.swift; sourceTree = ""; }; - C0A740000000000000000002 /* MainChatApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatApi.swift; sourceTree = ""; }; - C0A740000000000000000003 /* MainChatRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatRepository.swift; sourceTree = ""; }; - C0A740000000000000000004 /* MainChatViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatViewModel.swift; sourceTree = ""; }; - C0A740000000000000000005 /* MainChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatView.swift; sourceTree = ""; }; - C0A740000000000000000006 /* MainChatRoomListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatRoomListItem.swift; sourceTree = ""; }; - C0A740000000000000000007 /* MainChatFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChatFilter.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -5665,44 +5665,6 @@ path = Services; sourceTree = ""; }; - C0A74000000000000000000100 /* Chat */ = { - isa = PBXGroup; - children = ( - C0A74000000000000000000101 /* Models */, - C0A74000000000000000000102 /* Repository */, - C0A74000000000000000000103 /* Components */, - C0A740000000000000000004 /* MainChatViewModel.swift */, - C0A740000000000000000005 /* MainChatView.swift */, - C0A740000000000000000007 /* MainChatFilter.swift */, - ); - path = Chat; - sourceTree = ""; - }; - C0A74000000000000000000101 /* Models */ = { - isa = PBXGroup; - children = ( - C0A740000000000000000001 /* MainChatRoomsResponse.swift */, - ); - path = Models; - sourceTree = ""; - }; - C0A74000000000000000000102 /* Repository */ = { - isa = PBXGroup; - children = ( - C0A740000000000000000002 /* MainChatApi.swift */, - C0A740000000000000000003 /* MainChatRepository.swift */, - ); - path = Repository; - sourceTree = ""; - }; - C0A74000000000000000000103 /* Components */ = { - isa = PBXGroup; - children = ( - C0A740000000000000000006 /* MainChatRoomListItem.swift */, - ); - path = Components; - sourceTree = ""; - }; C0A700000000000000000101 /* Content */ = { isa = PBXGroup; children = ( @@ -5874,6 +5836,44 @@ path = Repository; sourceTree = ""; }; + C0A74000000000000000000100 /* Chat */ = { + isa = PBXGroup; + children = ( + C0A74000000000000000000101 /* Models */, + C0A74000000000000000000102 /* Repository */, + C0A74000000000000000000103 /* Components */, + C0A740000000000000000004 /* MainChatViewModel.swift */, + C0A740000000000000000005 /* MainChatView.swift */, + C0A740000000000000000007 /* MainChatFilter.swift */, + ); + path = Chat; + sourceTree = ""; + }; + C0A74000000000000000000101 /* Models */ = { + isa = PBXGroup; + children = ( + C0A740000000000000000001 /* MainChatRoomsResponse.swift */, + ); + path = Models; + sourceTree = ""; + }; + C0A74000000000000000000102 /* Repository */ = { + isa = PBXGroup; + children = ( + C0A740000000000000000002 /* MainChatApi.swift */, + C0A740000000000000000003 /* MainChatRepository.swift */, + ); + path = Repository; + sourceTree = ""; + }; + C0A74000000000000000000103 /* Components */ = { + isa = PBXGroup; + children = ( + C0A740000000000000000006 /* MainChatRoomListItem.swift */, + ); + path = Components; + sourceTree = ""; + }; C46DB3E329B349CBB3F472DF /* Views */ = { isa = PBXGroup; children = ( diff --git a/SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift b/SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift index 63f736a9..a486c091 100644 --- a/SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift +++ b/SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift @@ -326,23 +326,29 @@ struct CreatorChannelView: View { private var authConfirmDialog: some View { - SodaDialog( + SodaV2ActionModal( title: I18n.Chat.Auth.dialogTitle, - desc: I18n.Chat.Auth.dialogDescription, - confirmButtonTitle: I18n.Chat.Auth.goToVerification, - confirmButtonAction: { - isShowAuthConfirmView = false - isShowAuthView = true - }, - cancelButtonTitle: I18n.Common.cancel, - cancelButtonAction: { - isShowAuthConfirmView = false - pendingAction = nil - }, - textAlignment: .center + message: I18n.Chat.Auth.dialogDescription, + button1: SodaV2ActionModalButton( + label: I18n.Chat.Auth.goToVerification, + action: { + isShowAuthConfirmView = false + isShowAuthView = true + } + ), + button2: SodaV2ActionModalButton( + label: I18n.Common.cancel, + action: dismissAuthConfirmDialog + ), + onDimmedTap: dismissAuthConfirmDialog ) } + private func dismissAuthConfirmDialog() { + isShowAuthConfirmView = false + pendingAction = nil + } + private var fanTalkDeleteDialog: some View { SodaV2ActionModal( title: I18n.MemberChannel.cheersDeleteTitle, @@ -549,6 +555,10 @@ struct CreatorChannelView: View { AppState.shared.setAppStep(step: .contentViewSettings) } + private func dismissCommunityDeleteConfirm() { + communityViewModel.isShowDeleteConfirm = false + } + private func showDonationDialog() { guard let creatorId = viewModel.response?.creator.creatorId else { return } channelDonationViewModel.setCreatorId(creatorId, shouldFetch: false) @@ -594,19 +604,21 @@ struct CreatorChannelView: View { } if communityViewModel.isShowDeleteConfirm { - SodaDialog( + SodaV2ActionModal( title: I18n.Common.postDeleteTitle, - desc: I18n.Common.confirmDeleteQuestion, - confirmButtonTitle: I18n.Common.delete, - confirmButtonAction: { - communityViewModel.deleteCommunityPost(creatorId: creatorId) - communityViewModel.isShowDeleteConfirm = false - }, - cancelButtonTitle: I18n.Common.cancel, - cancelButtonAction: { - communityViewModel.isShowDeleteConfirm = false - }, - textAlignment: .center + message: I18n.Common.confirmDeleteQuestion, + button1: SodaV2ActionModalButton( + label: I18n.Common.delete, + action: { + communityViewModel.deleteCommunityPost(creatorId: creatorId) + communityViewModel.isShowDeleteConfirm = false + } + ), + button2: SodaV2ActionModalButton( + label: I18n.Common.cancel, + action: dismissCommunityDeleteConfirm + ), + onDimmedTap: dismissCommunityDeleteConfirm ) } diff --git a/SodaLive/Sources/V2/Main/MainView.swift b/SodaLive/Sources/V2/Main/MainView.swift index d7a3488b..f64526a3 100644 --- a/SodaLive/Sources/V2/Main/MainView.swift +++ b/SodaLive/Sources/V2/Main/MainView.swift @@ -282,31 +282,42 @@ struct MainView: View { } private var authConfirmDialog: some View { - SodaDialog( + SodaV2ActionModal( title: I18n.Main.Auth.dialogTitle, - desc: I18n.Main.Auth.liveEntryVerificationDescription, - confirmButtonTitle: I18n.Main.Auth.goToVerification, - confirmButtonAction: { - isShowAuthConfirmView = false - isShowAuthView = true - }, - cancelButtonTitle: I18n.Common.cancel, - cancelButtonAction: { - isShowAuthConfirmView = false - pendingAction = nil - }, - textAlignment: .center + message: I18n.Main.Auth.liveEntryVerificationDescription, + button1: SodaV2ActionModalButton( + label: I18n.Main.Auth.goToVerification, + action: { + isShowAuthConfirmView = false + isShowAuthView = true + } + ), + button2: SodaV2ActionModalButton( + label: I18n.Common.cancel, + action: dismissAuthConfirmDialog + ), + onDimmedTap: dismissAuthConfirmDialog ) } + private func dismissAuthConfirmDialog() { + isShowAuthConfirmView = false + pendingAction = nil + } + private var leaveLiveNavigationDialog: some View { - SodaDialog( + SodaV2ActionModal( title: I18n.Common.alertTitle, - desc: I18n.LiveRoom.leaveLiveForNavigationDesc, - confirmButtonTitle: I18n.Common.confirm, - confirmButtonAction: { confirmExternalNavigation() }, - cancelButtonTitle: I18n.Common.cancel, - cancelButtonAction: { cancelExternalNavigation() } + message: I18n.LiveRoom.leaveLiveForNavigationDesc, + button1: SodaV2ActionModalButton( + label: I18n.Common.confirm, + action: confirmExternalNavigation + ), + button2: SodaV2ActionModalButton( + label: I18n.Common.cancel, + action: cancelExternalNavigation + ), + onDimmedTap: cancelExternalNavigation ) } diff --git a/docs/20260708_V2_ActionModal_전환_조사/plan-task.md b/docs/20260708_V2_ActionModal_전환_조사/plan-task.md new file mode 100644 index 00000000..66bc8e90 --- /dev/null +++ b/docs/20260708_V2_ActionModal_전환_조사/plan-task.md @@ -0,0 +1,295 @@ +# V2 ActionModal 전환 조사 및 구현 + +## 목적 + +V2 폴더/Group에서 사용하는 `SodaDialog`, popup, modal, sheet, toast 계열 표시 지점을 조사하고, 1차 전환 후보를 `SodaV2ActionModal`로 구현한다. + +이번 문서는 조사, Phase 1 구현, Phase 2 판단, 검증 기록을 함께 다룬다. + +## 조사 범위 + +- 앱 코드: `SodaLive/Sources/V2/**` +- V2에서 직접 호출하는 shared dialog/component: + - `SodaLive/Sources/Dialog/**` + - `SodaLive/Sources/Report/**` + - `SodaLive/Sources/Main/**` + - `SodaLive/Sources/Content/Detail/**` + - `SodaLive/Sources/Settings/Notification/**` + - `SodaLive/Sources/Common/**` +- 제외: `Pods/**`, `generated/**`, `build/**` + +## 조사 명령 + +- `rg -n --glob '*.swift' 'SodaDialog\(' 'SodaLive/Sources/V2'` +- `rg -n --glob '*.swift' '(NotificationSettingsDialog\(|LivePaymentDialog\(|LiveRoomPasswordDialog\(|EventPopupDialogView\(|LiveRoomDonationDialogView\(|CommunityPostPurchaseDialog\(|CheersReportDialogView\(|CreatorCommunityReportView\(|CreatorCommunityMenuView\()' 'SodaLive/Sources/V2'` +- `rg -n --glob '*.swift' '(CreatorChannelSortContextPopup\(|CreatorChannelActionPopup\(|CreatorChannelReplyDetailActionPopup\(|\.sodaToast\(|\.sheet\(|fullScreenCover\()' 'SodaLive/Sources/V2'` +- `rg -n --glob '*.swift' 'SodaV2ActionModal\(' 'SodaLive/Sources/V2'` +- `ast-grep` pattern: `SodaDialog($$$)` on `SodaLive/Sources/V2` + +## 기준 컴포넌트 + +### `SodaV2ActionModal` + +- 정의: `SodaLive/Sources/V2/Component/Modal/SodaV2ActionModal.swift` +- API: + - `title: String` + - `message: String` + - `button1: SodaV2ActionModalButton?` + - `button2: SodaV2ActionModalButton?` + - `onDimmedTap: () -> Void` +- 버튼 규칙: + - `button1`은 primary action이며 `Color.soda400`로 표시된다. + - `button2`는 secondary action이며 `Color.white`로 표시된다. + - 렌더링 순서는 `button2`가 왼쪽, `button1`이 오른쪽이다. + - 버튼이 1개만 유효하면 primary 스타일로 단독 표시된다. + - `label` 또는 `action`이 `nil`이면 해당 버튼은 표시되지 않는다. +- UI 규칙: + - dimmed background: `Color.black.opacity(0.6)` + - modal width: `340` + - background: `Color.gray900` + - corner radius: `14` + +## 1차 전환 후보: 높음 + +아래 4곳은 V2 내부에서 `SodaDialog(...)`를 직접 호출하며, 모두 확인/취소 또는 삭제/취소 형태의 action-only dialog다. `SodaV2ActionModal` 전환 우선순위가 높다. + +### `SodaLive/Sources/V2/Main/MainView.swift` + +- `authConfirmDialog` + - 위치: `MainView.swift:285` + - 전환 전: `SodaDialog(title: I18n.Main.Auth.dialogTitle, desc: I18n.Main.Auth.liveEntryVerificationDescription, ...)` + - 상태: `isShowAuthConfirmView` + - 동작: 본인 인증 안내 후 `BootpayUI` 인증 화면으로 이동하거나 취소 시 `pendingAction`을 비운다. + - 적용 결과: `title`/`message`/인증 이동 primary/취소 secondary로 `SodaV2ActionModal`을 적용한다. dimmed tap은 V2 modal 동작에 맞춰 취소 흐름과 동일하게 처리한다. + +- `leaveLiveNavigationDialog` + - 위치: `MainView.swift:303` + - 전환 전: `SodaDialog(title: I18n.Common.alertTitle, desc: I18n.LiveRoom.leaveLiveForNavigationDesc, ...)` + - 상태: `isShowLeaveLiveNavigationDialog` + - 동작: 라이브 시청 중 외부 이동 시 `confirmExternalNavigation()` 또는 `cancelExternalNavigation()` 실행. + - 적용 결과: 확인 primary, 취소 secondary로 `SodaV2ActionModal`을 적용한다. dimmed tap은 V2 modal 동작에 맞춰 취소 흐름과 동일하게 처리한다. + +### `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` + +- `authConfirmDialog` + - 위치: `CreatorChannelView.swift:329` + - 전환 전: `SodaDialog(title: I18n.Chat.Auth.dialogTitle, desc: I18n.Chat.Auth.dialogDescription, ...)` + - 상태: `isShowAuthConfirmView` + - 동작: 인증 안내 후 `BootpayUI` 인증 화면으로 이동하거나 취소 시 `pendingAction`을 비운다. + - 적용 결과: `MainView.authConfirmDialog`와 같은 패턴으로 `SodaV2ActionModal`을 적용한다. dimmed tap은 V2 modal 동작에 맞춰 취소 흐름과 동일하게 처리한다. + +- community delete confirm + - 위치: `CreatorChannelView.swift:597` + - 전환 전: `SodaDialog(title: I18n.Common.postDeleteTitle, desc: I18n.Common.confirmDeleteQuestion, ...)` + - 상태: `communityViewModel.isShowDeleteConfirm` + - 동작: 확인 시 `communityViewModel.deleteCommunityPost(creatorId:)` 후 confirm 상태를 닫고, 취소 시 confirm 상태만 닫는다. + - 적용 결과: 삭제 primary, 취소 secondary로 `SodaV2ActionModal`을 적용한다. dimmed tap은 V2 modal 동작에 맞춰 취소와 동일하게 `isShowDeleteConfirm = false` 처리한다. + +## 이미 `SodaV2ActionModal`을 사용하는 기준 사례 + +구현 시 아래 사용처의 구조를 기준으로 삼는다. + +- `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift:347` + - `fanTalkDeleteDialog` + - 팬Talk 삭제 확인. + - 삭제 primary, 취소 secondary, dimmed tap dismiss 패턴. + +- `SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift:54` + - 댓글 삭제 확인. + - `viewModel.confirmDelete`와 `dismissDeleteDialog` 분리 패턴. + +- `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift:49` + - 답글/댓글 삭제 확인. + - `viewModel.confirmDelete`와 `dismissDeleteDialog` 분리 패턴. + +- `SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteView.swift:106` + - 작성 중 나가기 확인. + - primary가 항상 destructive는 아니며, 강조할 CTA를 `button1`에 둔다. + +## 별도 검토 후보 + +아래 항목은 V2에서 dialog/popup처럼 표시되지만, 입력 폼/도메인 UI/선택 UI가 포함되어 action-only `SodaV2ActionModal`로 단순 전환할 수 있는지 별도 판단이 필요하다. + +- `SodaLive/Sources/V2/Main/MainView.swift:66` + - `NotificationSettingsDialog()` + - 알림 설정 전용 dialog. 설정 UI 포함 여부 확인 필요. + +- `SodaLive/Sources/V2/Main/MainView.swift:74` + - `LivePaymentDialog(...)` + - 유료 라이브 결제/입장 확인. 시작/현재 시간 등 도메인 표시가 있어 단순 전환 전 UX 확인 필요. + +- `SodaLive/Sources/V2/Main/MainView.swift:88` + - `LiveRoomPasswordDialog(...)` + - 비밀번호 입력 field가 있어 action-only modal 전환 대상 가능성이 낮다. + +- `SodaLive/Sources/V2/Main/MainView.swift:98` + - `EventPopupDialogView(eventPopup:)` + - 이벤트 콘텐츠 팝업. 이미지/콘텐츠 표시 목적이면 `SodaV2ActionModal`과 목적이 다르다. + +- `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift:164` + - `LiveRoomDonationDialogView(...)` + - 후원 금액, 메시지, 비밀후원 입력이 포함된 form dialog. 단순 전환 대상 가능성이 낮다. + +- `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift:591` + - `CreatorCommunityReportView(...)` + - 신고 사유 선택 UI. 기존 report flow 재사용 범위로 유지할지 확인 필요. + +- `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift:614` + - `CommunityPostPurchaseDialog(...)` + - 유료 게시글 구매 확인. 금액 표시와 구매 action 중심이므로 V2 action modal 통일 여부를 UX 기준으로 결정한다. + +- `SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift:114` + - `CommunityPostPurchaseDialog(...)` + - 위와 동일한 구매 확인 dialog. `CreatorChannelView` 사용처와 함께 판단한다. + +- `SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift:129` + - `CheersReportDialogView(...)` + - 신고 사유 선택 dialog. 확인형 action modal과 목적이 다르므로 별도 검토. + +## 전환 대상에서 제외할 가능성이 높은 항목 + +아래 항목은 이름에 popup/modal/presented가 포함되거나 overlay로 표시되지만, 확인형 dialog가 아니므로 이번 `SodaV2ActionModal` 전환 대상에서는 제외하는 것이 안전하다. + +- `CreatorChannelSortContextPopup` + - 정의: `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelSortContextPopup.swift` + - 호출: + - `SodaLive/Sources/V2/Main/Content/All/MainContentAllView.swift:31` + - `SodaLive/Sources/V2/CreatorChannel/Live/CreatorChannelLiveTabView.swift:77` + - `SodaLive/Sources/V2/CreatorChannel/Audio/CreatorChannelAudioTabView.swift:87` + - `SodaLive/Sources/V2/CreatorChannel/Series/CreatorChannelSeriesTabView.swift:66` + - 이유: 정렬 선택용 context menu다. + +- `CreatorChannelActionPopup` / `CreatorChannelReplyDetailActionPopup` + - 정의: + - `SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift` + - `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailActionPopup.swift` + - 호출: + - `SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift:102` + - `SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailCommentListView.swift:35` + - `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailFeedView.swift:48` + - 이유: 수정/삭제 action 선택용 anchored context popup이며, 실제 삭제 확인은 별도 modal이 담당한다. + +- `.sheet(isPresented:)` + - `SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityTabView.swift:23` + - 이유: 댓글 리스트 화면 표시용 sheet다. + +- `.fullScreenCover(isPresented:)` + - `SodaLive/Sources/V2/Main/MainView.swift:118` + - `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift:190` + - 이유: `BootpayUI` 인증 화면 presentation이다. + +- `.sodaToast(...)` + - 이유: `isShowPopup` 네이밍을 쓰는 ViewModel이 많지만 실제 UI는 toast다. action modal 전환 대상이 아니다. + - 주요 호출 파일: + - `SodaLive/Sources/V2/Main/MainView.swift` + - `SodaLive/Sources/V2/Main/Chat/MainChatView.swift` + - `SodaLive/Sources/V2/Main/Content/Overview/ContentOverviewView.swift` + - `SodaLive/Sources/V2/Main/Content/All/MainContentAllView.swift` + - `SodaLive/Sources/V2/Main/Content/Ranking/MainContentRankingView.swift` + - `SodaLive/Sources/V2/Main/Content/Recommendation/MainContentRecommendationView.swift` + - `SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift` + - `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` + - `SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityTabView.swift` + - `SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift` + - `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift` + - `SodaLive/Sources/V2/CreatorChannel/Live/CreatorChannelLiveTabView.swift` + - `SodaLive/Sources/V2/CreatorChannel/Audio/CreatorChannelAudioTabView.swift` + - `SodaLive/Sources/V2/CreatorChannel/Series/CreatorChannelSeriesTabView.swift` + - `SodaLive/Sources/V2/CreatorChannel/Donation/CreatorChannelDonationTabView.swift` + - `SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift` + - `SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteView.swift` + +## 구현 체크리스트 + +### Phase 1: 1차 전환 후보 적용 + +- [x] **Task 1.1: `MainView.authConfirmDialog`를 `SodaV2ActionModal`로 전환** + - 대상 파일: `SodaLive/Sources/V2/Main/MainView.swift` + - 검증 기준: `rg "authConfirmDialog|SodaV2ActionModal|SodaDialog" SodaLive/Sources/V2/Main/MainView.swift` 실행 시 `authConfirmDialog`가 `SodaV2ActionModal`을 사용한다. + +- [x] **Task 1.2: `MainView.leaveLiveNavigationDialog`를 `SodaV2ActionModal`로 전환** + - 대상 파일: `SodaLive/Sources/V2/Main/MainView.swift` + - 검증 기준: `confirmExternalNavigation()` / `cancelExternalNavigation()` 호출 흐름이 유지된다. + +- [x] **Task 1.3: `CreatorChannelView.authConfirmDialog`를 `SodaV2ActionModal`로 전환** + - 대상 파일: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` + - 검증 기준: 인증 이동, 취소 시 `pendingAction = nil` 흐름이 유지된다. + +- [x] **Task 1.4: `CreatorChannelView` community delete confirm을 `SodaV2ActionModal`로 전환** + - 대상 파일: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` + - 검증 기준: 삭제 확인, 취소, dimmed tap에서 `communityViewModel.isShowDeleteConfirm` 상태가 올바르게 닫힌다. + +### Phase 2: 별도 검토 후보 판단 + +- [x] **Task 2.1: 구매/결제 dialog 전환 여부 결정** + - 대상 파일: + - `SodaLive/Sources/Dialog/LivePaymentDialog.swift` + - `SodaLive/Sources/Dialog/CommunityPostPurchaseDialog.swift` + - `SodaLive/Sources/V2/Main/MainView.swift` + - `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` + - `SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift` + - 검증 기준: action-only modal로 대체 가능한지, 금액/시간 표시가 필요한지 결정이 문서에 남는다. + +- [x] **Task 2.2: 입력/신고/이벤트 dialog 제외 여부 결정** + - 대상 파일: + - `SodaLive/Sources/Dialog/LiveRoomPasswordDialog.swift` + - `SodaLive/Sources/Content/Detail/LiveRoomDonationDialogView.swift` + - `SodaLive/Sources/Report/CheersReportDialogView.swift` + - `SodaLive/Sources/Main/EventPopupDialogView.swift` + - 검증 기준: `SodaV2ActionModal` 전환 제외 또는 별도 V2 컴포넌트화 여부가 문서에 남는다. + + +## Phase 2 판단 결과 + +### 구매/결제 dialog + +- `SodaLive/Sources/Dialog/CommunityPostPurchaseDialog.swift` + - 결정: 후속 구현에서 `SodaV2ActionModal` 전환 후보로 확정한다. + - 이유: 제목, 설명, 취소, `I18n.Dialog.CommunityPostPurchase.viewWithCans(can)` 확인 CTA만 있는 action-only 구매 확인 dialog다. + - V2 호출부: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`, `SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift` + - 후속 전환 방향: 구매 CTA를 `button1`, 취소를 `button2`, dimmed tap을 취소와 동일하게 `isShowing = false` 흐름으로 둔다. + +- `SodaLive/Sources/Dialog/LivePaymentDialog.swift` + - 결정: 현재 shared dialog를 유지한다. + - 이유: 기본 확인/취소 구조 외에 유료 라이브 시작 시간, 현재 시간, 추가 설명 영역을 조건부로 표시한다. 현재 `SodaV2ActionModal`의 `title/message/buttons` API로 단순 대체하면 시간 정보가 손실된다. + - V2 호출부: `SodaLive/Sources/V2/Main/MainView.swift` + - 후속 방향: V2 통일이 필요하면 `SodaV2ActionModal` 확장이 아니라 결제 전용 V2 dialog/component로 별도 설계한다. + +### 입력/신고/이벤트 dialog + +- `SodaLive/Sources/Dialog/LiveRoomPasswordDialog.swift` + - 결정: `SodaV2ActionModal` 전환 대상에서 제외한다. + - 이유: 비밀번호 입력 field, 키보드 대응, 캔 수 조건부 CTA가 있는 input dialog다. + - V2 호출부: `SodaLive/Sources/V2/Main/MainView.swift` + - 후속 방향: 필요 시 V2 input modal로 별도 컴포넌트화한다. + +- `SodaLive/Sources/Content/Detail/LiveRoomDonationDialogView.swift` + - 결정: `SodaV2ActionModal` 전환 대상에서 제외한다. + - 이유: 후원 금액 입력, 단축 금액 버튼, 비밀 후원 토글, 메시지 입력, 충전 이동, validation toast를 포함한 복합 form/bottom-sheet 성격이다. + - V2 호출부: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` + - 후속 방향: 필요 시 donation 전용 V2 modal/bottom-sheet로 별도 설계한다. + +- `SodaLive/Sources/Report/CheersReportDialogView.swift` + - 결정: `SodaV2ActionModal` 전환 대상에서 제외한다. + - 이유: 신고 사유 radio list 선택 상태가 필요한 selector dialog다. + - V2 호출부: `SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift` + - 후속 방향: 필요 시 V2 report selector 컴포넌트로 별도 설계한다. + +- `SodaLive/Sources/Main/EventPopupDialogView.swift` + - 결정: `SodaV2ActionModal` 전환 대상에서 제외한다. + - 이유: 원격 이미지 중심 콘텐츠 팝업이며 이미지 탭 상세 이동, 다시 보지 않기, 닫기 액션을 제공한다. 텍스트 확인형 action modal과 목적이 다르다. + - V2 호출부: `SodaLive/Sources/V2/Main/MainView.swift` + - 후속 방향: 필요 시 이벤트 팝업 전용 V2 component로 별도 설계한다. + +## 검증 기록 + +- 2026-07-08: `rg`와 `ast-grep`로 `SodaLive/Sources/V2` 내부 `SodaDialog(...)` 직접 호출이 4곳임을 확인했다. 대상은 `MainView.authConfirmDialog`, `MainView.leaveLiveNavigationDialog`, `CreatorChannelView.authConfirmDialog`, `CreatorChannelView` community delete confirm이다. +- 2026-07-08: `rg 'SodaV2ActionModal\(' SodaLive/Sources/V2`로 이미 적용된 기준 사례 4곳을 확인했다. 대상은 `CreatorChannelView.fanTalkDeleteDialog`, `CreatorChannelCommunityPostDetailView`, `CreatorChannelReplyDetailView`, `CreatorChannelFanTalkWriteView`다. +- 2026-07-08: `rg`로 V2에서 호출하는 shared dialog 컴포넌트를 확인하고, 구매/결제/입력/신고/이벤트 dialog는 action-only modal 전환 여부를 별도 검토 후보로 분리했다. +- 2026-07-08: `isShowPopup` 검색 결과의 대부분은 `.sodaToast(...)` 상태임을 확인해 `SodaV2ActionModal` 전환 대상에서 제외했다. +- 2026-07-08: Phase 1 구현으로 `MainView.authConfirmDialog`, `MainView.leaveLiveNavigationDialog`, `CreatorChannelView.authConfirmDialog`, `CreatorChannelView` community delete confirm을 `SodaV2ActionModal`로 전환했다. 확인/취소 action은 유지하고, dimmed tap은 V2 modal 동작에 맞춰 각 취소 흐름과 동일하게 연결했다. +- 2026-07-08: 전환 후 `rg -n --glob '*.swift' 'SodaDialog\(' 'SodaLive/Sources/V2'` 결과가 없어 V2 내부 직접 `SodaDialog(...)` 호출이 남지 않았음을 확인했다. +- 2026-07-08: `xcodebuild -workspace SodaLive.xcworkspace -scheme SodaLive-dev -configuration Debug -sdk iphonesimulator -derivedDataPath /tmp/SodaLiveDerivedData-SodaLiveDev CODE_SIGNING_ALLOWED=NO build` 실행 결과 `BUILD SUCCEEDED`를 확인했다. +- 2026-07-08: 기존 `SodaDialog`는 dimmed tap dismiss가 없지만, 이번 전환에서는 `SodaV2ActionModal`의 현재 UX에 맞춰 `onDimmedTap`을 각 취소 흐름과 동일하게 연결하는 것을 의도된 동작으로 정리했다. +- 2026-07-08: Phase 2 구매/결제 dialog 검토 결과, `CommunityPostPurchaseDialog`는 action-only 구매 확인 dialog이므로 후속 `SodaV2ActionModal` 전환 후보로 확정하고, `LivePaymentDialog`는 시작/현재 시간 정보 박스와 추가 설명이 있어 shared dialog 유지로 결정했다. +- 2026-07-08: Phase 2 입력/신고/이벤트 dialog 검토 결과, `LiveRoomPasswordDialog`, `LiveRoomDonationDialogView`, `CheersReportDialogView`, `EventPopupDialogView`는 입력/선택/이미지 콘텐츠 UI를 포함하므로 `SodaV2ActionModal` 전환 대상에서 제외하고 필요 시 별도 V2 컴포넌트화 대상으로 결정했다.