feat: 라이브 30분 연속 청취시 트래킹 API 호출 기능 추가
This commit is contained in:
		@@ -20,6 +20,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
 | 
			
		||||
    private let userRepository = UserRepository()
 | 
			
		||||
    private let reportRepository = ReportRepository()
 | 
			
		||||
    private let rouletteRepository = RouletteRepository()
 | 
			
		||||
    private let userActionRepository = UserActionRepository()
 | 
			
		||||
    private var subscription = Set<AnyCancellable>()
 | 
			
		||||
    
 | 
			
		||||
    @Published var isSpeakerMute = false
 | 
			
		||||
@@ -218,6 +219,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
 | 
			
		||||
    
 | 
			
		||||
    var timer: DispatchSourceTimer?
 | 
			
		||||
    var heartTimer: DispatchSourceTimer?
 | 
			
		||||
    var periodicPlaybackTimer: DispatchSourceTimer?
 | 
			
		||||
    
 | 
			
		||||
    var isAvailableLikeHeart = false
 | 
			
		||||
    
 | 
			
		||||
@@ -284,6 +286,8 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
 | 
			
		||||
        if containNoChatRoom() {
 | 
			
		||||
            startNoChatting()
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        startPeriodicPlaybackValidation()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func agoraConnectFail() {
 | 
			
		||||
@@ -1784,6 +1788,36 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func stopPeriodicPlaybackValidation() {
 | 
			
		||||
        periodicPlaybackTimer?.cancel()
 | 
			
		||||
        periodicPlaybackTimer = nil
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private func trackEventLiveContinuousListen30() {
 | 
			
		||||
        userActionRepository.trackEvent(actionType: .LIVE_CONTINUOUS_LISTEN_30)
 | 
			
		||||
            .sink { result in
 | 
			
		||||
                switch result {
 | 
			
		||||
                case .finished:
 | 
			
		||||
                    DEBUG_LOG("finish")
 | 
			
		||||
                case .failure(let error):
 | 
			
		||||
                    ERROR_LOG(error.localizedDescription)
 | 
			
		||||
                }
 | 
			
		||||
            } receiveValue: { response in
 | 
			
		||||
                DEBUG_LOG("트래킹 성공: \(response)")
 | 
			
		||||
            }
 | 
			
		||||
            .store(in: &subscription)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private func startPeriodicPlaybackValidation() {
 | 
			
		||||
        let queue = DispatchQueue.global(qos: .background)
 | 
			
		||||
        periodicPlaybackTimer = DispatchSource.makeTimerSource(queue: queue)
 | 
			
		||||
        periodicPlaybackTimer?.schedule(deadline: .now() + 1800, repeating: 1800)
 | 
			
		||||
        periodicPlaybackTimer?.setEventHandler { [weak self] in
 | 
			
		||||
            self?.trackEventLiveContinuousListen30()
 | 
			
		||||
        }
 | 
			
		||||
        periodicPlaybackTimer?.resume()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private func calculatePercentages(options: [RouletteItem]) -> [RoulettePreviewItem] {
 | 
			
		||||
        let updatedOptions = options.map { option in
 | 
			
		||||
            return RoulettePreviewItem(title: option.title, percent: "\(String(format: "%.2f", option.percentage))%")
 | 
			
		||||
 
 | 
			
		||||
@@ -433,6 +433,7 @@ struct LiveRoomViewV2: View {
 | 
			
		||||
            .onDisappear {
 | 
			
		||||
                UIApplication.shared.isIdleTimerDisabled = false
 | 
			
		||||
                NotificationCenter.default.removeObserver(self)
 | 
			
		||||
                viewModel.stopPeriodicPlaybackValidation()
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            ZStack {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										46
									
								
								SodaLive/Sources/UserAction/UserActionApi.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								SodaLive/Sources/UserAction/UserActionApi.swift
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
//
 | 
			
		||||
//  UserActionApi.swift
 | 
			
		||||
//  SodaLive
 | 
			
		||||
//
 | 
			
		||||
//  Created by klaus on 5/17/25.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import Foundation
 | 
			
		||||
import Moya
 | 
			
		||||
 | 
			
		||||
enum UserActionApi {
 | 
			
		||||
    case trackEvent(request: UserActionRequest)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extension UserActionApi: TargetType {
 | 
			
		||||
    var baseURL: URL {
 | 
			
		||||
        return URL(string: BASE_URL)!
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    var path: String {
 | 
			
		||||
        switch self {
 | 
			
		||||
        case .trackEvent:
 | 
			
		||||
            return "/user-action"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    var method: Moya.Method {
 | 
			
		||||
        switch self {
 | 
			
		||||
        case .trackEvent:
 | 
			
		||||
            return .post
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    var task: Moya.Task {
 | 
			
		||||
        switch self {
 | 
			
		||||
        case .trackEvent(let request):
 | 
			
		||||
            return .requestJSONEncodable(request)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    var headers: [String : String]? {
 | 
			
		||||
        return ["Authorization": "Bearer \(UserDefaults.string(forKey: UserDefaultsKey.token))"]
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								SodaLive/Sources/UserAction/UserActionRepository.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								SodaLive/Sources/UserAction/UserActionRepository.swift
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
//
 | 
			
		||||
//  UserActionRepository.swift
 | 
			
		||||
//  SodaLive
 | 
			
		||||
//
 | 
			
		||||
//  Created by klaus on 5/17/25.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import Foundation
 | 
			
		||||
import CombineMoya
 | 
			
		||||
import Combine
 | 
			
		||||
import Moya
 | 
			
		||||
 | 
			
		||||
final class UserActionRepository {
 | 
			
		||||
    private let api = MoyaProvider<UserActionApi>()
 | 
			
		||||
    
 | 
			
		||||
    func trackEvent(actionType: ActionType) -> AnyPublisher<Response, MoyaError> {
 | 
			
		||||
        return api.requestPublisher(.trackEvent(request: UserActionRequest(actionType: actionType)))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								SodaLive/Sources/UserAction/UserActionRequest.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								SodaLive/Sources/UserAction/UserActionRequest.swift
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
//
 | 
			
		||||
//  UserActionRequest.swift
 | 
			
		||||
//  SodaLive
 | 
			
		||||
//
 | 
			
		||||
//  Created by klaus on 5/17/25.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
struct UserActionRequest: Encodable {
 | 
			
		||||
    let actionType: ActionType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum ActionType: String, Encodable {
 | 
			
		||||
    case LIVE_CONTINUOUS_LISTEN_30
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user