From fb003ba25ec45ce268d01137cec5e54f5b6a88d9 Mon Sep 17 00:00:00 2001 From: jack Date: Tue, 7 Oct 2025 14:27:13 +0200 Subject: [PATCH] Extract DeliveryTrackingService and SystemMessagingService (-37 lines) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Continues god object decomposition with 2 more focused services. WHAT WAS EXTRACTED ================== 1. DeliveryTrackingService (73 lines) - updateMessageDeliveryStatus() logic - Prevents status downgrades (read → delivered) - Updates messages in both public and private chats - Triggers UI notifications 2. SystemMessagingService (42 lines) - createSystemMessage() factory methods - Consistent system message creation - Timestamp management From ChatViewModel (37 net lines reduced): - Delivery status update logic (54 lines) → thin wrapper - System message creation (inline code) → delegates to service - Message routing logic stays in ViewModel (needs channel access) NEW SERVICES =========== 1. bitchat/Services/DeliveryTrackingService.swift (73 lines) API: - updateStatus(messageID:status:messages:privateChats:notifyChange:) Features: - Prevents delivery status downgrades - Updates across all message stores - Clean separation of tracking logic 2. bitchat/Services/SystemMessagingService.swift (42 lines) API: - createSystemMessage(content:timestamp:) - createSystemMessage(content:timestamp:isRelay:originalSender:) Features: - Consistent system message factory - Reusable message creation - Timestamp management INTEGRATION =========== ChatViewModel changes: - Added deliveryTracking service - Added systemMessaging service - didReceiveReadReceipt() → delegates to deliveryTracking - didUpdateMessageDeliveryStatus() → delegates to deliveryTracking - updateMessageDeliveryStatus() → thin wrapper over deliveryTracking - addSystemMessage() → uses systemMessaging.createSystemMessage() - addMeshOnlySystemMessage() → uses systemMessaging.createSystemMessage() - addPublicSystemMessage() → uses systemMessaging.createSystemMessage() IMPACT ====== ChatViewModel: 5,394 → 5,357 lines (-0.7% this commit, -13.5% cumulative) Services added: +115 lines (2 new services) Tests: ✅ All 23 passing Build: ✅ Clean (6.21s) Cumulative god object reduction: -838 lines (13.5% total) Services extracted: 6 total 1. SpamFilterService (222) 2. ColorPaletteService (328) 3. MessageFormattingService (618) 4. GeohashParticipantsService (180) 5. DeliveryTrackingService (73) 6. SystemMessagingService (42) Total service lines: 1,463 Progress toward next milestone: Current: 5,357 lines Target: < 5,000 lines Remaining: 357 lines to extract TEST RESULTS ============ ✔ All 23 tests passing ✔ Build completes successfully ✔ Zero regressions ✔ Delivery tracking still works correctly ✔ System messages still display properly --- .../Services/DeliveryTrackingService.swift | 73 +++++++++++++++ bitchat/Services/SystemMessagingService.swift | 42 +++++++++ bitchat/ViewModels/ChatViewModel.swift | 93 ++++++------------- 3 files changed, 143 insertions(+), 65 deletions(-) create mode 100644 bitchat/Services/DeliveryTrackingService.swift create mode 100644 bitchat/Services/SystemMessagingService.swift diff --git a/bitchat/Services/DeliveryTrackingService.swift b/bitchat/Services/DeliveryTrackingService.swift new file mode 100644 index 00000000..45ecd0b2 --- /dev/null +++ b/bitchat/Services/DeliveryTrackingService.swift @@ -0,0 +1,73 @@ +// +// DeliveryTrackingService.swift +// bitchat +// +// Service for tracking message delivery and read status +// This is free and unencumbered software released into the public domain. +// + +import BitLogger +import Foundation + +/// Service that manages delivery status updates for messages +/// Prevents status downgrades (e.g., read → delivered) and maintains consistency +final class DeliveryTrackingService { + + // MARK: - Public API + + /// Update delivery status for a message, preventing downgrades + /// - Parameters: + /// - messageID: The message ID to update + /// - status: The new delivery status + /// - messages: Array of public messages (inout for mutation) + /// - privateChats: Dictionary of private chats (inout for mutation) + /// - notifyChange: Closure to trigger UI update + func updateStatus( + messageID: String, + status: DeliveryStatus, + messages: inout [BitchatMessage], + privateChats: inout [String: [BitchatMessage]], + notifyChange: @escaping () -> Void + ) { + // Update in main messages + if let index = messages.firstIndex(where: { $0.id == messageID }) { + let currentStatus = messages[index].deliveryStatus + if !shouldSkipUpdate(currentStatus: currentStatus, newStatus: status) { + messages[index].deliveryStatus = status + } + } + + // Update in private chats + for (peerID, chatMessages) in privateChats { + guard let index = chatMessages.firstIndex(where: { $0.id == messageID }) else { continue } + + let currentStatus = chatMessages[index].deliveryStatus + guard !shouldSkipUpdate(currentStatus: currentStatus, newStatus: status) else { continue } + + // Update delivery status + privateChats[peerID]?[index].deliveryStatus = status + } + + // Trigger UI update + DispatchQueue.main.async { + notifyChange() + } + } + + // MARK: - Private Helpers + + /// Check if we should skip a status update to prevent downgrades + private func shouldSkipUpdate(currentStatus: DeliveryStatus?, newStatus: DeliveryStatus) -> Bool { + guard let current = currentStatus else { return false } + + // Don't downgrade from read to delivered or sent + switch (current, newStatus) { + case (.read, .delivered): + return true + case (.read, .sent): + return true + default: + return false + } + } +} diff --git a/bitchat/Services/SystemMessagingService.swift b/bitchat/Services/SystemMessagingService.swift new file mode 100644 index 00000000..c4333444 --- /dev/null +++ b/bitchat/Services/SystemMessagingService.swift @@ -0,0 +1,42 @@ +// +// SystemMessagingService.swift +// bitchat +// +// Service for creating and managing system messages +// This is free and unencumbered software released into the public domain. +// + +import BitLogger +import Foundation + +/// Service that creates and routes system messages to appropriate channels +final class SystemMessagingService { + + // MARK: - Message Creation + + /// Create a basic system message + func createSystemMessage(content: String, timestamp: Date = Date()) -> BitchatMessage { + return BitchatMessage( + sender: "system", + content: content, + timestamp: timestamp, + isRelay: false + ) + } + + /// Create a system message with custom properties + func createSystemMessage( + content: String, + timestamp: Date = Date(), + isRelay: Bool = false, + originalSender: String? = nil + ) -> BitchatMessage { + return BitchatMessage( + sender: "system", + content: content, + timestamp: timestamp, + isRelay: isRelay, + originalSender: originalSender + ) + } +} diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index d30df816..6716b205 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -123,6 +123,16 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Computed property for backward compatibility var geohashPeople: [GeoPerson] { geohashParticipantsService.geohashPeople } + // MARK: - System Messaging + + /// Service for creating system messages + private let systemMessaging = SystemMessagingService() + + // MARK: - Delivery Tracking + + /// Service for tracking message delivery status + private let deliveryTracking = DeliveryTrackingService() + // MARK: - Published Properties @Published var messages: [BitchatMessage] = [] @@ -4089,68 +4099,31 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { return identityManager.isFavorite(fingerprint: fingerprint) } - // MARK: - Delivery Tracking - + // MARK: - Delivery Tracking (Delegated to DeliveryTrackingService) + func didReceiveReadReceipt(_ receipt: ReadReceipt) { - // Find the message and update its read status updateMessageDeliveryStatus(receipt.originalMessageID, status: .read(by: receipt.readerNickname, at: receipt.timestamp)) } - + func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) { updateMessageDeliveryStatus(messageID, status: status) } - + private func updateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) { - - // Helper function to check if we should skip this update - func shouldSkipUpdate(currentStatus: DeliveryStatus?, newStatus: DeliveryStatus) -> Bool { - guard let current = currentStatus else { return false } - - // Don't downgrade from read to delivered - switch (current, newStatus) { - case (.read, .delivered): - return true - case (.read, .sent): - return true - default: - return false + deliveryTracking.updateStatus( + messageID: messageID, + status: status, + messages: &messages, + privateChats: &privateChats, + notifyChange: { [weak self] in + self?.objectWillChange.send() } - } - - // Update in main messages - if let index = messages.firstIndex(where: { $0.id == messageID }) { - let currentStatus = messages[index].deliveryStatus - if !shouldSkipUpdate(currentStatus: currentStatus, newStatus: status) { - messages[index].deliveryStatus = status - } - } - - // Update in private chats - for (peerID, chatMessages) in privateChats { - guard let index = chatMessages.firstIndex(where: { $0.id == messageID }) else { continue } - - let currentStatus = chatMessages[index].deliveryStatus - guard !shouldSkipUpdate(currentStatus: currentStatus, newStatus: status) else { continue } - - // Update delivery status directly (BitchatMessage is a class/reference type) - privateChats[peerID]?[index].deliveryStatus = status - } - - // Trigger UI update for delivery status change - DispatchQueue.main.async { [weak self] in - self?.objectWillChange.send() - } - - } - - // MARK: - Helper for System Messages - private func addSystemMessage(_ content: String, timestamp: Date = Date()) { - let systemMessage = BitchatMessage( - sender: "system", - content: content, - timestamp: timestamp, - isRelay: false ) + } + + // MARK: - Helper for System Messages (Using SystemMessagingService) + private func addSystemMessage(_ content: String, timestamp: Date = Date()) { + let systemMessage = systemMessaging.createSystemMessage(content: content, timestamp: timestamp) messages.append(systemMessage) } @@ -4158,12 +4131,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { /// If mesh is currently active, also append to the visible `messages`. @MainActor private func addMeshOnlySystemMessage(_ content: String) { - let systemMessage = BitchatMessage( - sender: "system", - content: content, - timestamp: Date(), - isRelay: false - ) + let systemMessage = systemMessaging.createSystemMessage(content: content) // Persist to mesh timeline meshTimeline.append(systemMessage) trimMeshTimelineIfNeeded() @@ -4178,12 +4146,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { /// Also persists the message into the active channel's backing store so it survives timeline rebinds. @MainActor func addPublicSystemMessage(_ content: String) { - let systemMessage = BitchatMessage( - sender: "system", - content: content, - timestamp: Date(), - isRelay: false - ) + let systemMessage = systemMessaging.createSystemMessage(content: content) // Append to current visible messages messages.append(systemMessage) // Persist into the backing store for the active channel to survive rebinds