516 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			516 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
//
 | 
						|
//  ChatRoomViewModel.swift
 | 
						|
//  SodaLive
 | 
						|
//
 | 
						|
//  Created by klaus on 9/2/25.
 | 
						|
//
 | 
						|
 | 
						|
import Foundation
 | 
						|
import Combine
 | 
						|
import Moya
 | 
						|
 | 
						|
final class ChatRoomViewModel: ObservableObject {
 | 
						|
    // MARK: - Published State
 | 
						|
    @Published var isResetting: Bool = false
 | 
						|
    @Published var isLoading: Bool = false
 | 
						|
    @Published var errorMessage: String = ""
 | 
						|
    @Published var isShowPopup = false
 | 
						|
    
 | 
						|
    @Published var chatRoomBgImageId: Int = 0
 | 
						|
    @Published private(set) var characterId: Int64 = 0
 | 
						|
    @Published private(set) var characterProfileUrl: String = ""
 | 
						|
    @Published private(set) var characterName: String = "Character Name"
 | 
						|
    @Published private(set) var characterType: CharacterType = .Character
 | 
						|
    @Published private(set) var chatRoomBgImageUrl: String? = nil
 | 
						|
    @Published private(set) var roomId: Int = 0
 | 
						|
    
 | 
						|
    @Published private(set) var countdownText: String = "00:00:00"
 | 
						|
    @Published private(set) var showQuotaNoticeView: Bool = false
 | 
						|
    
 | 
						|
    @Published private(set) var showSendingMessage: Bool = false
 | 
						|
    
 | 
						|
    // MARK: - Message State
 | 
						|
    @Published var messageText: String = ""
 | 
						|
    @Published private(set) var messages: [ServerChatMessage] = []
 | 
						|
    
 | 
						|
    @Published var selectedMessage: ServerChatMessage? = nil
 | 
						|
    @Published var selectedMessageIndex: Int = -1
 | 
						|
    
 | 
						|
    @Published var isShowImageViewer = false
 | 
						|
    @Published var selectedImageIndex: Int = 0
 | 
						|
    
 | 
						|
    @Published var isHideBg = false {
 | 
						|
        didSet {
 | 
						|
            UserDefaults.standard.set(isHideBg, forKey: bgHideKey())
 | 
						|
        }
 | 
						|
    }
 | 
						|
    @Published var isShowingChatSettingsView = false
 | 
						|
    @Published var isShowingChangeBgView = false
 | 
						|
    @Published var isShowingChatResetConfirmDialog = false
 | 
						|
    
 | 
						|
    var ownedImageUrls: [String] {
 | 
						|
        return messages
 | 
						|
            .filter { $0.hasAccess }
 | 
						|
            .filter { $0.messageType.lowercased() == "image" && $0.imageUrl != nil && !$0.imageUrl.isNullOrBlank() }
 | 
						|
            .map { $0.imageUrl! }
 | 
						|
    }
 | 
						|
    
 | 
						|
    // MARK: - Private
 | 
						|
    private let userRepository = UserRepository()
 | 
						|
    private let repository = ChatRoomRepository()
 | 
						|
    private var subscription = Set<AnyCancellable>()
 | 
						|
    
 | 
						|
    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 {
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        if showSendingMessage {
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        let message = messageText.trimmingCharacters(in: .whitespacesAndNewlines)
 | 
						|
        messageText = ""
 | 
						|
        
 | 
						|
        let nowMs = Int64(Date().timeIntervalSince1970 * 1000)
 | 
						|
        
 | 
						|
        messages.append(
 | 
						|
            ServerChatMessage(
 | 
						|
                messageId: 0 - nowMs,
 | 
						|
                message: message,
 | 
						|
                profileImageUrl: "",
 | 
						|
                mine: true,
 | 
						|
                createdAt: nowMs,
 | 
						|
                messageType: "TEXT",
 | 
						|
                imageUrl: nil,
 | 
						|
                price: nil,
 | 
						|
                hasAccess: true
 | 
						|
            )
 | 
						|
        )
 | 
						|
        
 | 
						|
        showSendingMessage = true
 | 
						|
        repository.sendMessage(roomId: roomId, message: message)
 | 
						|
            .sink { result in
 | 
						|
                switch result {
 | 
						|
                case .finished:
 | 
						|
                    DEBUG_LOG("finish")
 | 
						|
                case .failure(let error):
 | 
						|
                    ERROR_LOG(error.localizedDescription)
 | 
						|
                }
 | 
						|
            } receiveValue: { [weak self] response in
 | 
						|
                let responseData = response.data
 | 
						|
                
 | 
						|
                do {
 | 
						|
                    let jsonDecoder = JSONDecoder()
 | 
						|
                    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(totalRemaining: data.totalRemaining, nextRechargeAtEpoch: data.nextRechargeAtEpoch)
 | 
						|
                    } else {
 | 
						|
                        if let message = decoded.message {
 | 
						|
                            self?.errorMessage = message
 | 
						|
                        } else {
 | 
						|
                            self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        self?.isShowPopup = true
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    self?.showSendingMessage = false
 | 
						|
                } catch {
 | 
						|
                    self?.showSendingMessage = false
 | 
						|
                    self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                    self?.isShowPopup = true
 | 
						|
                }
 | 
						|
            }
 | 
						|
            .store(in: &subscription)
 | 
						|
    }
 | 
						|
    
 | 
						|
    func enterRoom(roomId: Int) {
 | 
						|
        isLoading = true
 | 
						|
        self.roomId = roomId
 | 
						|
        self.isHideBg = UserDefaults.standard.bool(forKey: bgHideKey())
 | 
						|
        self.chatRoomBgImageId = getSavedBackgroundImageId() ?? 0
 | 
						|
        
 | 
						|
        repository.enterChatRoom(
 | 
						|
            roomId: roomId,
 | 
						|
            characterImageId: self.chatRoomBgImageId
 | 
						|
        )
 | 
						|
        .sink { result in
 | 
						|
            switch result {
 | 
						|
            case .finished:
 | 
						|
                DEBUG_LOG("finish")
 | 
						|
            case .failure(let error):
 | 
						|
                ERROR_LOG(error.localizedDescription)
 | 
						|
            }
 | 
						|
        } receiveValue: { [weak self] response in
 | 
						|
            let responseData = response.data
 | 
						|
            
 | 
						|
            do {
 | 
						|
                let jsonDecoder = JSONDecoder()
 | 
						|
                let decoded = try jsonDecoder.decode(ApiResponse<ChatRoomEnterResponse>.self, from: responseData)
 | 
						|
                
 | 
						|
                if let data = decoded.data, decoded.success {
 | 
						|
                    self?.characterId = data.character.characterId
 | 
						|
                    self?.characterName = data.character.name
 | 
						|
                    self?.characterType = data.character.characterType
 | 
						|
                    self?.characterProfileUrl = data.character.profileImageUrl
 | 
						|
                    
 | 
						|
                    self?.chatRoomBgImageUrl = data.bgImageUrl ?? data.character.profileImageUrl
 | 
						|
                    self?.messages.insert(contentsOf: data.messages.sorted(), at: 0)
 | 
						|
                    
 | 
						|
                    self?.hasMoreMessages = data.hasMoreMessages
 | 
						|
                    self?.nextCursor = data.messages.last?.messageId
 | 
						|
                    
 | 
						|
                    self?.updateQuota(totalRemaining: data.totalRemaining, nextRechargeAtEpoch: data.nextRechargeAtEpoch)
 | 
						|
                } else {
 | 
						|
                    if let message = decoded.message {
 | 
						|
                        self?.errorMessage = message
 | 
						|
                    } else {
 | 
						|
                        self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    self?.isShowPopup = true
 | 
						|
                }
 | 
						|
                
 | 
						|
                self?.isLoading = false
 | 
						|
            } catch {
 | 
						|
                self?.isLoading = false
 | 
						|
                self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                self?.isShowPopup = true
 | 
						|
            }
 | 
						|
        }
 | 
						|
        .store(in: &subscription)
 | 
						|
    }
 | 
						|
    
 | 
						|
    func getMemberInfo() {
 | 
						|
        userRepository.getMemberInfo()
 | 
						|
            .sink { result in
 | 
						|
                switch result {
 | 
						|
                case .finished:
 | 
						|
                    DEBUG_LOG("finish")
 | 
						|
                case .failure(let error):
 | 
						|
                    ERROR_LOG(error.localizedDescription)
 | 
						|
                }
 | 
						|
            } receiveValue: { response in
 | 
						|
                let responseData = response.data
 | 
						|
                
 | 
						|
                do {
 | 
						|
                    let jsonDecoder = JSONDecoder()
 | 
						|
                    let decoded = try jsonDecoder.decode(ApiResponse<GetMemberInfoResponse>.self, from: responseData)
 | 
						|
                    
 | 
						|
                    if let data = decoded.data, decoded.success {
 | 
						|
                        UserDefaults.set(data.can, forKey: .can)
 | 
						|
                        UserDefaults.set(data.point, forKey: .point)
 | 
						|
                    }
 | 
						|
                } catch {
 | 
						|
                    print(error)
 | 
						|
                }
 | 
						|
            }
 | 
						|
            .store(in: &subscription)
 | 
						|
    }
 | 
						|
    
 | 
						|
    func purchaseChatMessage() {
 | 
						|
        guard let selectedMessage = selectedMessage else {
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        isLoading = true
 | 
						|
        
 | 
						|
        repository.purchaseMessage(roomId: roomId, messageId: selectedMessage.messageId)
 | 
						|
            .sink { result in
 | 
						|
                switch result {
 | 
						|
                case .finished:
 | 
						|
                    DEBUG_LOG("finish")
 | 
						|
                case .failure(let error):
 | 
						|
                    ERROR_LOG(error.localizedDescription)
 | 
						|
                }
 | 
						|
            } receiveValue: { [weak self] response in
 | 
						|
                let responseData = response.data
 | 
						|
                
 | 
						|
                do {
 | 
						|
                    let jsonDecoder = JSONDecoder()
 | 
						|
                    let decoded = try jsonDecoder.decode(ApiResponse<ServerChatMessage>.self, from: responseData)
 | 
						|
                    
 | 
						|
                    if let data = decoded.data, decoded.success {
 | 
						|
                        self?.messages.insert(data, at: self?.selectedMessageIndex ?? 0)
 | 
						|
                        self?.messages.remove(at: (self?.selectedMessageIndex ?? 0) + 1)
 | 
						|
                        
 | 
						|
                        self?.selectedMessage = nil
 | 
						|
                        self?.selectedMessageIndex = -1
 | 
						|
                    } else {
 | 
						|
                        if let message = decoded.message {
 | 
						|
                            self?.errorMessage = message
 | 
						|
                        } else {
 | 
						|
                            self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        self?.isShowPopup = true
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    self?.isLoading = false
 | 
						|
                } catch {
 | 
						|
                    self?.isLoading = false
 | 
						|
                    self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                    self?.isShowPopup = true
 | 
						|
                }
 | 
						|
            }
 | 
						|
            .store(in: &subscription)
 | 
						|
    }
 | 
						|
    
 | 
						|
    func purchaseChatQuota() {
 | 
						|
        isLoading = true
 | 
						|
        
 | 
						|
        repository.purchaseChatQuota()
 | 
						|
            .sink { result in
 | 
						|
                switch result {
 | 
						|
                case .finished:
 | 
						|
                    DEBUG_LOG("finish")
 | 
						|
                case .failure(let error):
 | 
						|
                    ERROR_LOG(error.localizedDescription)
 | 
						|
                }
 | 
						|
            } receiveValue: { [weak self] response in
 | 
						|
                let responseData = response.data
 | 
						|
                
 | 
						|
                do {
 | 
						|
                    let jsonDecoder = JSONDecoder()
 | 
						|
                    let decoded = try jsonDecoder.decode(ApiResponse<ChatQuotaStatusResponse>.self, from: responseData)
 | 
						|
                    
 | 
						|
                    if let data = decoded.data, decoded.success {
 | 
						|
                        self?.updateQuota(totalRemaining: data.totalRemaining, nextRechargeAtEpoch: data.nextRechargeAtEpoch)
 | 
						|
                        
 | 
						|
                        let can = UserDefaults.int(forKey: .can)
 | 
						|
                        UserDefaults.set(can - 30, forKey: .can)
 | 
						|
                    } else {
 | 
						|
                        if let message = decoded.message {
 | 
						|
                            self?.errorMessage = message
 | 
						|
                        } else {
 | 
						|
                            self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        self?.isShowPopup = true
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    self?.isLoading = false
 | 
						|
                } catch {
 | 
						|
                    self?.isLoading = false
 | 
						|
                    self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                    self?.isShowPopup = true
 | 
						|
                }
 | 
						|
            }
 | 
						|
            .store(in: &subscription)
 | 
						|
    }
 | 
						|
    
 | 
						|
    func resetChatRoom() {
 | 
						|
        isResetting = true
 | 
						|
        
 | 
						|
        repository.resetChatRoom(roomId: roomId)
 | 
						|
            .sink { result in
 | 
						|
                switch result {
 | 
						|
                case .finished:
 | 
						|
                    DEBUG_LOG("finish")
 | 
						|
                case .failure(let error):
 | 
						|
                    ERROR_LOG(error.localizedDescription)
 | 
						|
                }
 | 
						|
            } receiveValue: { [weak self] response in
 | 
						|
                let responseData = response.data
 | 
						|
                self?.isResetting = false
 | 
						|
                
 | 
						|
                do {
 | 
						|
                    let jsonDecoder = JSONDecoder()
 | 
						|
                    let decoded = try jsonDecoder.decode(ApiResponse<CreateChatRoomResponse>.self, from: responseData)
 | 
						|
                    
 | 
						|
                    if let data = decoded.data, decoded.success {
 | 
						|
                        self?.resetData()
 | 
						|
                        self?.getMemberInfo()
 | 
						|
                        self?.enterRoom(roomId: data.chatRoomId)
 | 
						|
                    } else {
 | 
						|
                        if let message = decoded.message {
 | 
						|
                            self?.errorMessage = message
 | 
						|
                        } else {
 | 
						|
                            self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        self?.isShowPopup = true
 | 
						|
                    }
 | 
						|
                } catch {
 | 
						|
                    ERROR_LOG(String(describing: error))
 | 
						|
                    self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                    self?.isShowPopup = true
 | 
						|
                }
 | 
						|
            }
 | 
						|
            .store(in: &subscription)
 | 
						|
    }
 | 
						|
    
 | 
						|
    func showImageViewer(_ imageUrl: String?) {
 | 
						|
        if let imageUrl = imageUrl {
 | 
						|
            selectedImageIndex = ownedImageUrls.firstIndex(of: imageUrl) ?? 0
 | 
						|
            isShowImageViewer = true
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    func setBackgroundImage(imageItem: CharacterImageListItemResponse) {
 | 
						|
        UserDefaults.standard.set(imageItem.id, forKey: bgImageIdKey())
 | 
						|
        chatRoomBgImageUrl = imageItem.imageUrl
 | 
						|
        chatRoomBgImageId = imageItem.id
 | 
						|
    }
 | 
						|
    
 | 
						|
    private func resetData() {
 | 
						|
        characterProfileUrl = ""
 | 
						|
        characterName = "Character Name"
 | 
						|
        characterType = .Character
 | 
						|
        chatRoomBgImageUrl  = nil
 | 
						|
        roomId = 0
 | 
						|
        
 | 
						|
        countdownText = "00:00:00"
 | 
						|
        showQuotaNoticeView = false
 | 
						|
        
 | 
						|
        showSendingMessage = false
 | 
						|
        
 | 
						|
        messageText = ""
 | 
						|
        messages = []
 | 
						|
        selectedMessage = nil
 | 
						|
        selectedMessageIndex = -1
 | 
						|
        isShowImageViewer = false
 | 
						|
        selectedImageIndex = 0
 | 
						|
        
 | 
						|
        isShowingChatSettingsView = false
 | 
						|
        isShowingChangeBgView = false
 | 
						|
        isShowingChatResetConfirmDialog = false
 | 
						|
    }
 | 
						|
    
 | 
						|
    private func checkQuotaStatus() {
 | 
						|
        isLoading = true
 | 
						|
        
 | 
						|
        repository.getChatQuotaStatus()
 | 
						|
            .sink { result in
 | 
						|
                switch result {
 | 
						|
                case .finished:
 | 
						|
                    DEBUG_LOG("finish")
 | 
						|
                case .failure(let error):
 | 
						|
                    ERROR_LOG(error.localizedDescription)
 | 
						|
                }
 | 
						|
            } receiveValue: { [weak self] response in
 | 
						|
                let responseData = response.data
 | 
						|
                
 | 
						|
                do {
 | 
						|
                    let jsonDecoder = JSONDecoder()
 | 
						|
                    let decoded = try jsonDecoder.decode(ApiResponse<ChatQuotaStatusResponse>.self, from: responseData)
 | 
						|
                    
 | 
						|
                    if let data = decoded.data, decoded.success {
 | 
						|
                        self?.updateQuota(totalRemaining: data.totalRemaining, nextRechargeAtEpoch: data.nextRechargeAtEpoch)
 | 
						|
                    } else {
 | 
						|
                        if let message = decoded.message {
 | 
						|
                            self?.errorMessage = message
 | 
						|
                        } else {
 | 
						|
                            self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        self?.isShowPopup = true
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    self?.isLoading = false
 | 
						|
                } catch {
 | 
						|
                    self?.isLoading = false
 | 
						|
                    self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                    self?.isShowPopup = true
 | 
						|
                }
 | 
						|
            }
 | 
						|
            .store(in: &subscription)
 | 
						|
    }
 | 
						|
    
 | 
						|
    private func updateQuota(totalRemaining: Int, nextRechargeAtEpoch: Int64?) {
 | 
						|
        isLoading = true
 | 
						|
        stopTimer()
 | 
						|
        
 | 
						|
        // epoch 없음 → 카운트다운 비표시
 | 
						|
        guard let nextRechargeAtEpoch else {
 | 
						|
            countdownText = "00:00:00"
 | 
						|
            showQuotaNoticeView = false
 | 
						|
            isLoading = false
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        // 즉시 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? {
 | 
						|
        let imageId = UserDefaults.standard.integer(forKey: bgImageIdKey())
 | 
						|
        return imageId > 0 ? imageId : nil
 | 
						|
    }
 | 
						|
    
 | 
						|
    private func bgImageIdKey() -> String {
 | 
						|
        return "chat_bg_image_id_room_\(roomId)"
 | 
						|
    }
 | 
						|
    
 | 
						|
    private func bgHideKey() -> String {
 | 
						|
        return "chat_bg_hide_room_\(roomId)"
 | 
						|
    }
 | 
						|
}
 |