feat(chat): 채팅 쿼터 광고 충전을 추가한다
This commit is contained in:
@@ -28,7 +28,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
@Published private(set) var countdownText: String = "00:00:00"
|
||||
@Published private(set) var totalRemaining: Int = 0
|
||||
@Published private(set) var showQuotaNoticeView: Bool = false
|
||||
|
||||
@Published private(set) var showSendingMessage: Bool = false
|
||||
@@ -72,8 +72,6 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
private var hasMoreMessages: Bool = true
|
||||
private var nextCursor: Int64? = nil
|
||||
|
||||
private var timer: Timer?
|
||||
|
||||
// MARK: - Actions
|
||||
func sendMessage() {
|
||||
guard !messageText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
@@ -125,7 +123,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<SendChatMessageResponse>.self, from: responseData)
|
||||
if let data = decoded.data, decoded.success {
|
||||
self.messages.append(contentsOf: data.messages)
|
||||
self.updateQuota(nextRechargeAtEpoch: data.nextRechargeAtEpoch)
|
||||
self.updateQuota(totalRemaining: data.totalRemaining)
|
||||
} else {
|
||||
self.errorMessage = decoded.message ?? I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
@@ -177,7 +175,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
self?.hasMoreMessages = data.hasMoreMessages
|
||||
self?.nextCursor = data.messages.last?.messageId
|
||||
|
||||
self?.updateQuota(nextRechargeAtEpoch: data.nextRechargeAtEpoch)
|
||||
self?.updateQuota(totalRemaining: data.totalRemaining)
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self?.errorMessage = message
|
||||
@@ -275,10 +273,25 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
func purchaseChatQuota() {
|
||||
func purchaseChatQuota(canOption: ChatRoomQuotaCanOption) {
|
||||
purchaseChatQuota(chargeType: .can, canOption: canOption)
|
||||
}
|
||||
|
||||
func showRewardedAdForChatQuota() {
|
||||
_Concurrency.Task {
|
||||
await YandexRewardedAdManager.shared.showAdIfAvailable(for: .chatRoomQuota) { [weak self] in
|
||||
self?.purchaseChatQuota(chargeType: .ad, canOption: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func purchaseChatQuota(
|
||||
chargeType: ChatRoomQuotaChargeType,
|
||||
canOption: ChatRoomQuotaCanOption?
|
||||
) {
|
||||
isLoading = true
|
||||
|
||||
repository.purchaseChatQuota(roomId: roomId)
|
||||
repository.purchaseChatQuota(roomId: roomId, chargeType: chargeType, canOption: canOption)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { result in
|
||||
switch result {
|
||||
@@ -295,10 +308,12 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<ChatQuotaStatusResponse>.self, from: responseData)
|
||||
|
||||
if let data = decoded.data, decoded.success {
|
||||
self?.updateQuota(nextRechargeAtEpoch: data.nextRechargeAtEpoch)
|
||||
self?.updateQuota(totalRemaining: data.totalRemaining)
|
||||
|
||||
let can = UserDefaults.int(forKey: .can)
|
||||
UserDefaults.set(can - 30, forKey: .can)
|
||||
if let canOption {
|
||||
let can = UserDefaults.int(forKey: .can)
|
||||
UserDefaults.set(can - canOption.needCan, forKey: .can)
|
||||
}
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self?.errorMessage = message
|
||||
@@ -385,7 +400,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
chatRoomBgImageUrl = nil
|
||||
roomId = 0
|
||||
|
||||
countdownText = "00:00:00"
|
||||
totalRemaining = 0
|
||||
showQuotaNoticeView = false
|
||||
|
||||
showSendingMessage = false
|
||||
@@ -421,7 +436,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<ChatQuotaStatusResponse>.self, from: responseData)
|
||||
|
||||
if let data = decoded.data, decoded.success {
|
||||
self?.updateQuota(nextRechargeAtEpoch: data.nextRechargeAtEpoch)
|
||||
self?.updateQuota(totalRemaining: data.totalRemaining)
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self?.errorMessage = message
|
||||
@@ -442,78 +457,18 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
private func updateQuota(nextRechargeAtEpoch: Int64?) {
|
||||
isLoading = true
|
||||
stopTimer()
|
||||
|
||||
// epoch 없음 → 카운트다운 비표시
|
||||
guard let nextRechargeAtEpoch else {
|
||||
countdownText = "00:00:00"
|
||||
showQuotaNoticeView = false
|
||||
isLoading = false
|
||||
return
|
||||
private func updateQuota(totalRemaining: Int) {
|
||||
self.totalRemaining = totalRemaining
|
||||
showQuotaNoticeView = totalRemaining <= 0
|
||||
prepareRewardedAdIfNeeded(totalRemaining: totalRemaining)
|
||||
}
|
||||
|
||||
private func prepareRewardedAdIfNeeded(totalRemaining: Int) {
|
||||
guard totalRemaining <= 1 else { return }
|
||||
|
||||
_Concurrency.Task {
|
||||
await YandexRewardedAdManager.shared.preloadAd(for: .chatRoomQuota)
|
||||
}
|
||||
|
||||
// 즉시 1회 갱신
|
||||
let remainMs = remainingMs(to: nextRechargeAtEpoch)
|
||||
updateCountdownText(remainMs)
|
||||
|
||||
// 이미 0이면 종료 처리
|
||||
guard remainMs > 0 else {
|
||||
checkQuotaStatus()
|
||||
return
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
showQuotaNoticeView = true
|
||||
|
||||
// 타이머 시작 (1초마다 갱신)
|
||||
startTimer(targetEpoch: nextRechargeAtEpoch)
|
||||
}
|
||||
|
||||
private func updateCountdownText(_ remainMs: Int64) {
|
||||
countdownText = remainMs > 0 ? formatMillisToHms(remainMs) : "00:00:00"
|
||||
}
|
||||
|
||||
private func startTimer(targetEpoch: Int64) {
|
||||
stopTimer()
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
let remain = self.remainingMs(to: targetEpoch)
|
||||
self.updateCountdownText(remain)
|
||||
if remain == 0 {
|
||||
self.stopTimer()
|
||||
self.checkQuotaStatus()
|
||||
}
|
||||
}
|
||||
if let t = timer { RunLoop.main.add(t, forMode: .common) }
|
||||
}
|
||||
|
||||
func stopTimer() {
|
||||
timer?.invalidate()
|
||||
timer = nil
|
||||
}
|
||||
|
||||
private func remainingMs(to epoch: Int64) -> Int64 {
|
||||
let ms = normalizeToMs(epoch)
|
||||
let nowMs = Int64(Date().timeIntervalSince1970 * 1000)
|
||||
let fudgeMs: Int64 = 5000
|
||||
|
||||
// Kotlin 로직과 동일하게 표시 보정 적용
|
||||
return max(ms - nowMs + fudgeMs, 0)
|
||||
}
|
||||
|
||||
/// 초 단위/밀리초 단위 혼용 대비
|
||||
private func normalizeToMs(_ epoch: Int64) -> Int64 {
|
||||
epoch < 1_000_000_000_000 ? epoch * 1000 : epoch
|
||||
}
|
||||
|
||||
private func formatMillisToHms(_ ms: Int64) -> String {
|
||||
let total = ms / 1000
|
||||
let h = total / 3600
|
||||
let m = (total % 3600) / 60
|
||||
let s = total % 60
|
||||
return String(format: "%02d:%02d:%02d", h, m, s)
|
||||
}
|
||||
|
||||
private func getSavedBackgroundImageId() -> Int? {
|
||||
|
||||
Reference in New Issue
Block a user