diff --git a/SodaLive/Sources/Common/YandexAdSupport.swift b/SodaLive/Sources/Common/YandexAdSupport.swift index 6e27161a..d5ccfbab 100644 --- a/SodaLive/Sources/Common/YandexAdSupport.swift +++ b/SodaLive/Sources/Common/YandexAdSupport.swift @@ -11,7 +11,6 @@ import YandexMobileAds enum YandexBannerPlacement { case liveTab case liveDetail - case contentDetail case creatorCommunityAll case seriesMainHome case seriesMainDayOfWeek @@ -23,10 +22,6 @@ enum YandexBannerPlacement { case chatTalkTabTop } -enum YandexInterstitialPlacement { - case contentDetail -} - enum YandexRewardedPlacement { case chatRoomQuota } @@ -39,8 +34,6 @@ enum YandexAdUnitIdProvider { YANDEX_LIVE_TAB_BANNER_AD_UNIT_ID case .liveDetail: YANDEX_LIVE_DETAIL_BANNER_AD_UNIT_ID - case .contentDetail: - YANDEX_CONTENT_DETAIL_BANNER_AD_UNIT_ID case .creatorCommunityAll: YANDEX_CREATOR_COMMUNITY_ALL_BANNER_AD_UNIT_ID case .seriesMainHome: @@ -62,13 +55,6 @@ enum YandexAdUnitIdProvider { } } - static func interstitial(for placement: YandexInterstitialPlacement) -> String { - switch placement { - case .contentDetail: - YANDEX_CONTENT_DETAIL_INTERSTITIAL_AD_UNIT_ID - } - } - static func rewarded(for placement: YandexRewardedPlacement) -> String { switch placement { case .chatRoomQuota: @@ -204,117 +190,6 @@ private struct YandexInlineBannerContainer: UIViewRepresentable { } } -@MainActor -final class YandexInterstitialAdManager: NSObject { - - static let shared = YandexInterstitialAdManager() - - private var interstitialAd: InterstitialAd? - private var interstitialAdLoader: InterstitialAdLoader? - private var currentPlacement: YandexInterstitialPlacement? - private var pendingAction: (@MainActor () -> Void)? - private var isLoading = false - - func preloadAd(for placement: YandexInterstitialPlacement) { - guard !isLoading else { - return - } - - if currentPlacement == placement, interstitialAd != nil { - return - } - - let loader = InterstitialAdLoader() - interstitialAdLoader = loader - interstitialAd = nil - currentPlacement = placement - isLoading = true - - Task { - do { - let loadedAd = try await loader.loadAd(with: AdRequest(adUnitID: YandexAdUnitIdProvider.interstitial(for: placement))) - guard currentPlacement == placement else { - isLoading = false - return - } - - loadedAd.delegate = self - interstitialAd = loadedAd - } catch { - if currentPlacement == placement { - interstitialAd = nil - } - } - - if currentPlacement == placement { - isLoading = false - } - } - } - - func showAdIfAvailable(for placement: YandexInterstitialPlacement, then action: @escaping @MainActor () -> Void) { - guard let presenter = presentingViewController(), let interstitialAd, currentPlacement == placement else { - action() - preloadAd(for: placement) - return - } - - pendingAction = action - interstitialAd.show(from: presenter) - } - - private func completePendingAction() { - let action = pendingAction - pendingAction = nil - interstitialAd = nil - action?() - - if let currentPlacement { - preloadAd(for: currentPlacement) - } - } - - private func presentingViewController() -> UIViewController? { - guard - let rootViewController = UIApplication.shared.connectedScenes - .compactMap({ $0 as? UIWindowScene }) - .flatMap({ $0.windows }) - .first(where: { $0.isKeyWindow })? - .rootViewController - else { - return nil - } - - var topViewController = rootViewController - - while let presentedViewController = topViewController.presentedViewController { - topViewController = presentedViewController - } - - return topViewController - } -} - -extension YandexInterstitialAdManager: InterstitialAdDelegate { - - func interstitialAdDidShow(_ interstitialAd: InterstitialAd) { - } - - func interstitialAdDidDismiss(_ interstitialAd: InterstitialAd) { - completePendingAction() - } - - func interstitialAdDidClick(_ interstitialAd: InterstitialAd) { - } - - func interstitialAd(_ interstitialAd: InterstitialAd, didTrackImpression impressionData: ImpressionData?) { - } - - func interstitialAd(_ interstitialAd: InterstitialAd, didFailToShow error: Error) { - completePendingAction() - } -} - @MainActor final class YandexRewardedAdManager: NSObject { diff --git a/SodaLive/Sources/Content/Detail/ContentDetailPlayView.swift b/SodaLive/Sources/Content/Detail/ContentDetailPlayView.swift index fdedd476..8ef6f1db 100644 --- a/SodaLive/Sources/Content/Detail/ContentDetailPlayView.swift +++ b/SodaLive/Sources/Content/Detail/ContentDetailPlayView.swift @@ -199,10 +199,6 @@ struct ContentDetailPlayView: View { .frame(width: screenSize().width - 40) } .onAppear { - Task { - await YandexInterstitialAdManager.shared.preloadAd(for: .contentDetail) - } - if !isPlaying() { stopTimer() } @@ -245,22 +241,7 @@ struct ContentDetailPlayView: View { ) } - guard shouldShowInterstitialBeforePlayback(showPreviewAlert: showPreviewAlert) else { - playAction() - return - } - - Task { - await YandexInterstitialAdManager.shared.showAdIfAvailable(for: .contentDetail, then: playAction) - } - } - - private func shouldShowInterstitialBeforePlayback(showPreviewAlert: Bool) -> Bool { - if contentPlayManager.contentId == audioContent.contentId { - return false - } - - return audioContent.price <= 0 || showPreviewAlert + playAction() } private func sliderRange() -> ClosedRange { diff --git a/SodaLive/Sources/Content/Detail/ContentDetailView.swift b/SodaLive/Sources/Content/Detail/ContentDetailView.swift index 5f2bd5d4..99bc2116 100644 --- a/SodaLive/Sources/Content/Detail/ContentDetailView.swift +++ b/SodaLive/Sources/Content/Detail/ContentDetailView.swift @@ -91,10 +91,6 @@ struct ContentDetailView: View { .padding(.top, 13.3) .padding(.horizontal, 13.3) } - - YandexInlineBannerView(placement: .contentDetail) - .padding(.top, 13.3) - ContentDetailInfoView( isExpandDescription: $viewModel.isExpandDescription, isShowPreviewAlert: $viewModel.isShowPreviewAlert, diff --git a/docs/plan-task/20260428_Yandex광고화면배치구현.md b/docs/plan-task/20260428_Yandex광고화면배치구현.md index a70b2c93..e17f2cb3 100644 --- a/docs/plan-task/20260428_Yandex광고화면배치구현.md +++ b/docs/plan-task/20260428_Yandex광고화면배치구현.md @@ -9,6 +9,9 @@ - [x] `ContentDetailView` 오픈예정/theme 표시와 다음화/이전화 사이 배너 삽입 - [x] `ContentDetailPlayView` 무료 재생/미리듣기 시작 전 전면광고 삽입 - [x] 빌드 및 정적 검증 기록 추가 +- [x] 2026-07-13 `ContentDetailView` 콘텐츠 상세 Yandex 인라인 배너 제거 +- [x] 2026-07-13 `ContentDetailPlayView` 콘텐츠 상세 재생 전 Yandex 전면광고 제거 +- [x] 2026-07-13 콘텐츠 상세 전용 Yandex placement/ad unit/전면광고 매니저 미사용 코드 제거 ## 작업 기준 @@ -103,3 +106,18 @@ - 결과: - 페이지별 광고 ID를 독립적으로 설정할 수 있는 구조로 전환됨 - completion closure 기반 `self` 캡처 경고 제거 대상 구조를 async/await로 대체함 + +- 2026-07-13 / 콘텐츠 상세 Yandex 광고 제거 + - 무엇: 콘텐츠 상세 화면의 Yandex 인라인 배너와 재생 전 Yandex 전면광고 흐름을 제거했다. + - 왜: 콘텐츠 상세 페이지에서 광고 영역과 재생 전 전면광고를 더 이상 사용하지 않기 위해서다. + - 어떻게: + - `SodaLive/Sources/Content/Detail/ContentDetailView.swift`에서 `YandexInlineBannerView(placement: .contentDetail)` 호출을 제거했다. + - `SodaLive/Sources/Content/Detail/ContentDetailPlayView.swift`에서 `YandexInterstitialAdManager` preload/show 호출과 `shouldShowInterstitialBeforePlayback` 가드를 제거했다. + - `SodaLive/Sources/Common/YandexAdSupport.swift`에서 콘텐츠 상세 전용 배너 placement, interstitial placement, `YandexInterstitialAdManager`를 제거했다. + - `SodaLive/Sources/Utils/Constants.swift`, `SodaLive/Sources/Debug/Utils/Constants.swift`에서 콘텐츠 상세 배너/전면광고 ad unit 상수를 제거했다. + - `rg -n "YandexInlineBannerView\\(placement: \\.contentDetail\\)|YandexInterstitialPlacement|YandexInterstitialAdManager|YANDEX_CONTENT_DETAIL_(BANNER|INTERSTITIAL)_AD_UNIT_ID|shouldShowInterstitialBeforePlayback|showAdIfAvailable\\(for: \\.contentDetail|preloadAd\\(for: \\.contentDetail" SodaLive/Sources` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` + - 결과: + - 콘텐츠 상세 전용 Yandex 배너/전면광고 참조가 `SodaLive/Sources`에 남지 않음 + - `SodaLive-dev` Debug 빌드 성공 + - 다른 화면의 Yandex 인라인 배너와 채팅 rewarded 광고 지원 코드는 유지 diff --git a/docs/plan-task/20260430_콘텐츠상세광고후재생수정.md b/docs/plan-task/20260430_콘텐츠상세광고후재생수정.md index d1f7b900..e56a562a 100644 --- a/docs/plan-task/20260430_콘텐츠상세광고후재생수정.md +++ b/docs/plan-task/20260430_콘텐츠상세광고후재생수정.md @@ -38,3 +38,15 @@ - `SodaLive` Debug 빌드 성공 - `lsp_diagnostics`는 SourceKit 단독 해석에서 `YandexMobileAds` 모듈 미해결을 보고했으나, 실제 Xcode 빌드는 두 스킴 모두 성공했다. - 실제 기기에서 광고 시청 후 오디오가 시작되는 수동 QA는 이 환경에서 앱 로그인/콘텐츠/광고 표시 조작이 불가해 수행하지 못했다. + +- 2026-07-13 / 후속 변경: 콘텐츠 상세 재생 전 광고 제거 + - 무엇: 콘텐츠 상세 재생 전 Yandex 전면광고 preload/show 흐름과 관련 공용 interstitial 매니저를 제거했다. + - 왜: 콘텐츠 상세에서 재생 전 광고를 더 이상 사용하지 않도록 정책이 변경되었기 때문이다. + - 어떻게: + - `SodaLive/Sources/Content/Detail/ContentDetailPlayView.swift`에서 `YandexInterstitialAdManager` 호출과 광고 표시 조건 함수를 제거했다. + - `SodaLive/Sources/Common/YandexAdSupport.swift`에서 `YandexInterstitialAdManager`, `YandexInterstitialPlacement`, interstitial ad unit 조회를 제거했다. + - `rg`로 콘텐츠 상세 전용 interstitial 참조가 남지 않는지 확인했다. + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` + - 결과: + - 콘텐츠 상세 재생 탭은 광고 분기 없이 기존 재생 액션을 바로 실행한다. + - `SodaLive-dev` Debug 빌드 성공