import Foundation import Combine import Moya enum CreatorChannelCommunityViewMode { case list case thumbnail } final class CreatorChannelCommunityViewModel: ObservableObject { private let repository = CreatorChannelCommunityRepository() private let communityRepository = CreatorCommunityRepository() private let reportRepository = ReportRepository() private var subscription = Set() private let pageSize = 20 private var latestRequestId = 0 @Published var isLoading = false @Published var isLoadingNextPage = false @Published var response: CreatorChannelCommunityTabResponse? @Published var communityPosts = [CreatorChannelCommunityPostItem]() @Published var viewMode: CreatorChannelCommunityViewMode = .list @Published var page = 0 @Published var size = 20 @Published var hasNext = false @Published var hasLoaded = false @Published var errorMessage = "" @Published var isShowPopup = false @Published var isShowCommentListView = false { didSet { if !isShowCommentListView { selectedPostId = 0 isShowSecret = false } } } @Published var isShowReportMenu = false @Published var isShowReportView = false { didSet { if !isShowReportView { selectedPostId = 0 } } } @Published var isShowDeleteConfirm = false { didSet { if !isShowDeleteConfirm { selectedPostId = 0 } } } @Published var isShowPostPurchaseView = false { didSet { if !isShowPostPurchaseView { selectedPostId = 0 selectedPostPrice = 0 } } } @Published var selectedPostId = 0 @Published var selectedPostPrice = 0 @Published var selectedPostIsPinned = false @Published var isShowSecret = false var communityPostCount: Int { response?.communityPostCount ?? 0 } func fetchFirstPage(creatorId: Int) { fetchCommunity(creatorId: creatorId, page: 0, isNextPage: false) } func fetchNextPageIfNeeded(creatorId: Int, currentItem: CreatorChannelCommunityPostItem) { guard communityPosts.last?.postId == currentItem.postId else { return } guard hasNext, isLoadingNextPage == false, isLoading == false else { return } fetchCommunity(creatorId: creatorId, page: page + 1, isNextPage: true) } func toggleViewMode() { viewMode = viewMode == .list ? .thumbnail : .list } func isPaidLocked(_ post: CreatorChannelCommunityPostItem, isOwnPost: Bool) -> Bool { post.price > 0 && post.existOrdered == false && isOwnPost == false } func openCommentList(post: CreatorChannelCommunityPostItem) { selectedPostId = post.postId isShowSecret = post.price > 0 && post.existOrdered && post.creatorId != UserDefaults.int(forKey: .userId) isShowCommentListView = true } func openReportMenu(post: CreatorChannelCommunityPostItem) { selectedPostId = post.postId selectedPostPrice = post.price selectedPostIsPinned = post.isPinned isShowReportMenu = true } func openPurchaseDialog(post: CreatorChannelCommunityPostItem) { selectedPostId = post.postId selectedPostPrice = post.price isShowPostPurchaseView = true } func communityPostLike(postId: Int) { communityRepository.communityPostLike(postId: postId) .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { _ in } .store(in: &subscription) } func report(reason: String) { guard selectedPostId > 0 else { return } isLoading = true let request = ReportRequest(type: .COMMUNITY_POST, reason: reason, communityPostId: selectedPostId) reportRepository.report(request: request) .sink { [weak self] result in guard let self else { return } switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) self.applyFailureState() } } receiveValue: { [weak self] response in guard let self else { return } self.isLoading = false self.selectedPostId = 0 do { let decoded = try JSONDecoder().decode(ApiResponseWithoutData.self, from: response.data) self.errorMessage = decoded.message ?? I18n.Common.commonError self.isShowPopup = true } catch { ERROR_LOG(error.localizedDescription) self.errorMessage = I18n.Common.commonError self.isShowPopup = true } } .store(in: &subscription) } func deleteCommunityPost(creatorId: Int) { guard selectedPostId > 0 else { return } isLoading = true let request = ModifyCommunityPostRequest(creatorCommunityId: selectedPostId, isActive: false) var multipartData = [MultipartFormData]() let encoder = JSONEncoder() encoder.outputFormatting = .withoutEscapingSlashes guard let jsonData = try? encoder.encode(request) else { applyFailureState() return } multipartData.append(MultipartFormData(provider: .data(jsonData), name: "request")) communityRepository.modifyCommunityPost(parameters: multipartData) .sink { [weak self] result in guard let self else { return } switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) self.applyFailureState() } } receiveValue: { [weak self] response in guard let self else { return } self.isLoading = false self.selectedPostId = 0 do { let decoded = try JSONDecoder().decode(ApiResponseWithoutData.self, from: response.data) if decoded.success { self.errorMessage = I18n.Explorer.deleted self.isShowPopup = true self.fetchFirstPage(creatorId: creatorId) } else { self.errorMessage = decoded.message ?? I18n.Common.commonError self.isShowPopup = true } } catch { ERROR_LOG(error.localizedDescription) self.errorMessage = I18n.Common.commonError self.isShowPopup = true } } .store(in: &subscription) } func updateCommunityPostFixed(creatorId: Int) { guard selectedPostId > 0 else { return } isLoading = true let request = UpdateCommunityPostFixedRequest(postId: selectedPostId, isFixed: !selectedPostIsPinned) communityRepository.updateCommunityPostFixed(request: request) .sink { [weak self] result in guard let self else { return } switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) self.applyFailureState() } } receiveValue: { [weak self] response in guard let self else { return } self.isLoading = false do { let decoded = try JSONDecoder().decode(ApiResponseWithoutData.self, from: response.data) if decoded.success { self.fetchFirstPage(creatorId: creatorId) } else { self.errorMessage = decoded.message ?? I18n.Common.commonError self.isShowPopup = true } } catch { ERROR_LOG(error.localizedDescription) self.errorMessage = I18n.Common.commonError self.isShowPopup = true } } .store(in: &subscription) } func purchaseCommunityPost(creatorId: Int) { guard selectedPostId > 0 else { return } isLoading = true communityRepository.purchaseCommunityPost(postId: selectedPostId) .sink { [weak self] result in guard let self else { return } switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) self.applyFailureState() } } receiveValue: { [weak self] response in guard let self else { return } self.isLoading = false self.selectedPostId = 0 do { let decoded = try JSONDecoder().decode(ApiResponseWithoutData.self, from: response.data) if decoded.success { self.fetchFirstPage(creatorId: creatorId) } else { self.errorMessage = decoded.message ?? I18n.Common.commonError self.isShowPopup = true } } catch { ERROR_LOG(error.localizedDescription) self.fetchFirstPage(creatorId: creatorId) } } .store(in: &subscription) } private func fetchCommunity(creatorId: Int, page requestPage: Int, isNextPage: Bool) { latestRequestId += 1 let requestId = latestRequestId if isNextPage { isLoadingNextPage = true } else { isLoading = true } repository.getCommunity(creatorId: creatorId, page: requestPage, size: pageSize) .sink { [weak self] result in guard let self else { return } guard requestId == self.latestRequestId else { return } switch result { case .finished: DEBUG_LOG("finish") self.isLoading = false self.isLoadingNextPage = false case .failure(let error): ERROR_LOG(error.localizedDescription) self.applyFailureState() } } receiveValue: { [weak self] response in guard let self else { return } guard requestId == self.latestRequestId else { return } do { let decoded = try JSONDecoder().decode(ApiResponse.self, from: response.data) if let data = decoded.data, decoded.success { self.applySuccessState(data, isNextPage: isNextPage) self.hasLoaded = true } else { self.errorMessage = decoded.message ?? I18n.Common.commonError self.isShowPopup = true } } catch { ERROR_LOG(error.localizedDescription) self.errorMessage = I18n.Common.commonError self.isShowPopup = true } self.isLoading = false self.isLoadingNextPage = false } .store(in: &subscription) } private func applySuccessState(_ data: CreatorChannelCommunityTabResponse, isNextPage: Bool) { response = data page = data.page size = data.size hasNext = data.hasNext if isNextPage { communityPosts.append(contentsOf: data.communityPosts) } else { communityPosts = data.communityPosts } } private func applyFailureState() { errorMessage = I18n.Common.commonError isShowPopup = true hasLoaded = true isLoading = false isLoadingNextPage = false } }