ios_app_v2/yobble/ViewModels/PrivateChatViewModel.swift
2025-10-08 05:55:32 +03:00

129 lines
4.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
@MainActor
final class PrivateChatViewModel: ObservableObject {
@Published private(set) var messages: [MessageItem] = []
@Published private(set) var isInitialLoading: Bool = false
@Published private(set) var isLoadingMore: Bool = false
@Published var errorMessage: String?
private let chatService: ChatService
private let chatId: String
private let pageSize: Int
private var hasMore: Bool = true
private var didLoadInitially: Bool = false
init(chatId: String, chatService: ChatService = ChatService(), pageSize: Int = 30) {
self.chatId = chatId
self.chatService = chatService
self.pageSize = pageSize
}
func loadInitialHistory(force: Bool = false) {
if !force && didLoadInitially { return }
guard !isInitialLoading else { return }
isInitialLoading = true
errorMessage = nil
hasMore = true
chatService.fetchPrivateChatHistory(chatId: chatId, beforeMessageId: nil, limit: pageSize) { [weak self] result in
guard let self else { return }
switch result {
case .success(let data):
self.hasMore = data.hasMore
self.messages = Self.merge(existing: [], newMessages: data.items)
self.didLoadInitially = true
case .failure(let error):
self.errorMessage = self.message(for: error)
}
self.isInitialLoading = false
}
}
func refresh() {
didLoadInitially = false
loadInitialHistory(force: true)
}
func loadMoreIfNeeded(for message: MessageItem) {
guard didLoadInitially, !isInitialLoading, hasMore, !isLoadingMore else { return }
guard let first = messages.first, first.id == message.id else { return }
isLoadingMore = true
chatService.fetchPrivateChatHistory(chatId: chatId, beforeMessageId: message.id, limit: pageSize) { [weak self] result in
guard let self else { return }
switch result {
case .success(let data):
self.hasMore = data.hasMore
if !data.items.isEmpty {
self.messages = Self.merge(existing: self.messages, newMessages: data.items)
}
case .failure(let error):
self.errorMessage = self.message(for: error)
}
self.isLoadingMore = false
}
}
private func message(for error: Error) -> String {
if let chatError = error as? ChatServiceError {
return chatError.errorDescription ?? NSLocalizedString("Не удалось загрузить историю чата.", comment: "")
}
if let networkError = error as? NetworkError {
switch networkError {
case .unauthorized:
return NSLocalizedString("Сессия истекла. Войдите снова.", comment: "")
case .invalidURL, .noResponse:
return NSLocalizedString("Ошибка соединения с сервером.", comment: "")
case .network(let underlying):
return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), underlying.localizedDescription)
case .server(let statusCode, _):
return String(format: NSLocalizedString("Ошибка сервера (%@).", comment: ""), "\(statusCode)")
}
}
return NSLocalizedString("Неизвестная ошибка. Попробуйте позже.", comment: "")
}
private static func merge(existing: [MessageItem], newMessages: [MessageItem]) -> [MessageItem] {
var combined: [MessageItem] = []
combined.reserveCapacity(existing.count + newMessages.count)
var seen: Set<String> = []
for message in newMessages {
if seen.insert(message.id).inserted {
combined.append(message)
}
}
for message in existing {
if seen.insert(message.id).inserted {
combined.append(message)
}
}
combined.sort(by: compare)
return combined
}
private static func compare(lhs: MessageItem, rhs: MessageItem) -> Bool {
if let lhsDate = lhs.createdAt, let rhsDate = rhs.createdAt, lhsDate != rhsDate {
return lhsDate < rhsDate
}
if let lhsId = Int(lhs.messageId), let rhsId = Int(rhs.messageId), lhsId != rhsId {
return lhsId < rhsId
}
return lhs.messageId < rhs.messageId
}
}