feat(yandex-ads): 화면별 Yandex 광고 배치를 추가한다

This commit is contained in:
Yu Sung
2026-04-28 11:59:46 +09:00
parent 0813b64bc9
commit 503468f713
8 changed files with 512 additions and 96 deletions

View File

@@ -23,8 +23,8 @@ struct ContentDetailPlayView: View {
@State private var progress: TimeInterval = 0
@State private var timer = Timer.publish(every: 0.01, on: .main, in: .common).autoconnect()
var body: some View {
VStack(alignment: .leading, spacing: 8) {
var body: some View {
VStack(alignment: .leading, spacing: 8) {
ZStack {
KFImage(URL(string: audioContent.coverImageUrl))
.cancelOnDisappear(true)
@@ -88,9 +88,9 @@ struct ContentDetailPlayView: View {
ContentPlayerPlayManager.shared.resetPlayer()
contentPlayManager.pauseAudio()
}
} else {
if isAlertPreview {
HStack(spacing: 4) {
} else {
if isAlertPreview {
HStack(spacing: 4) {
Image("ic_noti_play")
.resizable()
.frame(width: 24, height: 24)
@@ -98,60 +98,21 @@ struct ContentDetailPlayView: View {
Text(I18n.ContentDetail.preview)
.appFont(size: 16.7, weight: .medium)
.foregroundColor(Color.white)
}
.padding(.vertical, 13.3)
.frame(minWidth: 212)
.background(Color.black.opacity(0.4))
.cornerRadius(46.7)
.onTapGesture {
ContentPlayerPlayManager.shared.resetPlayer()
contentPlayManager.startTimer = startTimer
contentPlayManager.stopTimer = stopTimer
contentPlayManager.playAudio(
contentId: audioContent.contentId,
title: audioContent.title,
nickname: audioContent.creator.nickname,
coverImage: audioContent.coverImageUrl,
contentUrl: audioContent.contentUrl,
isFree: audioContent.price <= 0,
isPreview: !audioContent.existOrdered && audioContent.price > 0
)
isShowPreviewAlert = true
recentContentViewModel.insertRecentContent(
contentId: Int64(audioContent.contentId),
coverImageUrl: audioContent.coverImageUrl,
title: audioContent.title,
creatorNickname: audioContent.creator.nickname
)
}
} else {
Image("btn_audio_content_play")
.onTapGesture {
ContentPlayerPlayManager.shared.resetPlayer()
contentPlayManager.startTimer = startTimer
contentPlayManager.stopTimer = stopTimer
contentPlayManager.playAudio(
contentId: audioContent.contentId,
title: audioContent.title,
nickname: audioContent.creator.nickname,
coverImage: audioContent.coverImageUrl,
contentUrl: audioContent.contentUrl,
isFree: audioContent.price <= 0,
isPreview: !audioContent.existOrdered && audioContent.price > 0
)
recentContentViewModel.insertRecentContent(
contentId: Int64(audioContent.contentId),
coverImageUrl: audioContent.coverImageUrl,
title: audioContent.title,
creatorNickname: audioContent.creator.nickname
)
}
}
}
}
.padding(.vertical, 13.3)
.frame(minWidth: 212)
.background(Color.black.opacity(0.4))
.cornerRadius(46.7)
.onTapGesture {
handlePlayTap(showPreviewAlert: true)
}
} else {
Image("btn_audio_content_play")
.onTapGesture {
handlePlayTap(showPreviewAlert: false)
}
}
}
if !isAlertPreview {
Image("ic_player_next_10")
@@ -236,29 +197,79 @@ struct ContentDetailPlayView: View {
}
}
.frame(width: screenSize().width - 40)
}
.onAppear {
if !isPlaying() {
stopTimer()
}
}
}
.onAppear {
Task {
await YandexInterstitialAdManager.shared.preloadAd(for: .contentDetail)
}
if !isPlaying() {
stopTimer()
}
}
.onReceive(timer) { _ in
guard let player = contentPlayManager.player, !isEditing else { return }
self.progress = player.currentTime
}
}
private func isPlaying() -> Bool {
return contentPlayManager.contentId == audioContent.contentId && contentPlayManager.isPlaying
}
private func sliderRange() -> ClosedRange<Double> {
if audioContent.contentId == contentPlayManager.contentId {
return 0...contentPlayManager.duration
} else {
return 0...0
}
}
private func isPlaying() -> Bool {
return contentPlayManager.contentId == audioContent.contentId && contentPlayManager.isPlaying
}
private func handlePlayTap(showPreviewAlert: Bool) {
let playAction: @MainActor () -> Void = {
ContentPlayerPlayManager.shared.resetPlayer()
contentPlayManager.startTimer = startTimer
contentPlayManager.stopTimer = stopTimer
contentPlayManager.playAudio(
contentId: audioContent.contentId,
title: audioContent.title,
nickname: audioContent.creator.nickname,
coverImage: audioContent.coverImageUrl,
contentUrl: audioContent.contentUrl,
isFree: audioContent.price <= 0,
isPreview: !audioContent.existOrdered && audioContent.price > 0
)
if showPreviewAlert {
isShowPreviewAlert = true
}
recentContentViewModel.insertRecentContent(
contentId: Int64(audioContent.contentId),
coverImageUrl: audioContent.coverImageUrl,
title: audioContent.title,
creatorNickname: audioContent.creator.nickname
)
}
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
}
private func sliderRange() -> ClosedRange<Double> {
if audioContent.contentId == contentPlayManager.contentId {
return 0...contentPlayManager.duration
} else {
return 0...0
}
}
private func getProgress() -> String {
if audioContent.contentId == contentPlayManager.contentId {