116 lines
3.8 KiB
Swift
116 lines
3.8 KiB
Swift
import Foundation
|
|
import Combine
|
|
|
|
final class IncomingMessageCenter: ObservableObject {
|
|
@Published private(set) var banner: IncomingMessageBanner?
|
|
@Published var presentedChat: PrivateChatListItem?
|
|
var currentUserId: String?
|
|
var activeChatId: String?
|
|
|
|
private var dismissWorkItem: DispatchWorkItem?
|
|
private var cancellables = Set<AnyCancellable>()
|
|
private let notificationCenter: NotificationCenter
|
|
|
|
init(notificationCenter: NotificationCenter = .default) {
|
|
self.notificationCenter = notificationCenter
|
|
notificationCenter.publisher(for: .socketDidReceivePrivateMessage)
|
|
.compactMap { $0.object as? MessageItem }
|
|
.receive(on: RunLoop.main)
|
|
.sink { [weak self] message in
|
|
self?.handleIncoming(message)
|
|
}
|
|
.store(in: &cancellables)
|
|
}
|
|
|
|
func dismissBanner() {
|
|
dismissWorkItem?.cancel()
|
|
dismissWorkItem = nil
|
|
banner = nil
|
|
}
|
|
|
|
func openCurrentChat() {
|
|
guard let banner else { return }
|
|
activeChatId = banner.message.chatId
|
|
presentedChat = makeChatItem(from: banner.message)
|
|
dismissBanner()
|
|
}
|
|
|
|
private func handleIncoming(_ message: MessageItem) {
|
|
if let currentUserId, message.senderId == currentUserId {
|
|
return
|
|
}
|
|
|
|
if let activeChatId, activeChatId == message.chatId {
|
|
return
|
|
}
|
|
|
|
if let presentedChat, presentedChat.chatId == message.chatId {
|
|
return
|
|
}
|
|
|
|
banner = buildBanner(from: message)
|
|
scheduleDismiss()
|
|
}
|
|
|
|
private func buildBanner(from message: MessageItem) -> IncomingMessageBanner {
|
|
let sender = senderName(for: message)
|
|
let preview = messagePreview(for: message)
|
|
return IncomingMessageBanner(message: message, senderName: sender, messagePreview: preview)
|
|
}
|
|
|
|
private func senderName(for message: MessageItem) -> String {
|
|
let candidates: [String?] = [
|
|
message.senderData?.customName,
|
|
message.senderData?.fullName,
|
|
message.senderData?.login.map { "@\($0)" }
|
|
]
|
|
|
|
for candidate in candidates {
|
|
if let value = candidate?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty {
|
|
return value
|
|
}
|
|
}
|
|
|
|
return message.senderId
|
|
}
|
|
|
|
private func messagePreview(for message: MessageItem) -> String {
|
|
if let content = message.content?.trimmingCharacters(in: .whitespacesAndNewlines), !content.isEmpty {
|
|
return content
|
|
}
|
|
|
|
switch message.messageType.lowercased() {
|
|
case "image":
|
|
return NSLocalizedString("Изображение", comment: "Image message placeholder")
|
|
case "audio":
|
|
return NSLocalizedString("Аудио", comment: "Audio message placeholder")
|
|
case "video":
|
|
return NSLocalizedString("Видео", comment: "Video message placeholder")
|
|
default:
|
|
return NSLocalizedString("Новое сообщение", comment: "Default banner subtitle")
|
|
}
|
|
}
|
|
|
|
private func makeChatItem(from message: MessageItem) -> PrivateChatListItem {
|
|
let profile = message.senderData
|
|
return PrivateChatListItem(
|
|
chatId: message.chatId,
|
|
chatType: .privateChat,
|
|
chatData: profile,
|
|
lastMessage: message,
|
|
createdAt: message.createdAt,
|
|
unreadCount: 0
|
|
)
|
|
}
|
|
|
|
private func scheduleDismiss(after delay: TimeInterval = 5) {
|
|
dismissWorkItem?.cancel()
|
|
let workItem = DispatchWorkItem { [weak self] in
|
|
self?.banner = nil
|
|
self?.dismissWorkItem = nil
|
|
}
|
|
dismissWorkItem = workItem
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem)
|
|
}
|
|
}
|