mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 16:05:19 +00:00
Extract DeliveryTrackingService and SystemMessagingService (-37 lines)
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
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -123,6 +123,16 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
// Computed property for backward compatibility
|
// Computed property for backward compatibility
|
||||||
var geohashPeople: [GeoPerson] { geohashParticipantsService.geohashPeople }
|
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
|
// MARK: - Published Properties
|
||||||
|
|
||||||
@Published var messages: [BitchatMessage] = []
|
@Published var messages: [BitchatMessage] = []
|
||||||
@@ -4089,68 +4099,31 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
return identityManager.isFavorite(fingerprint: fingerprint)
|
return identityManager.isFavorite(fingerprint: fingerprint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Delivery Tracking
|
// MARK: - Delivery Tracking (Delegated to DeliveryTrackingService)
|
||||||
|
|
||||||
func didReceiveReadReceipt(_ receipt: ReadReceipt) {
|
func didReceiveReadReceipt(_ receipt: ReadReceipt) {
|
||||||
// Find the message and update its read status
|
|
||||||
updateMessageDeliveryStatus(receipt.originalMessageID, status: .read(by: receipt.readerNickname, at: receipt.timestamp))
|
updateMessageDeliveryStatus(receipt.originalMessageID, status: .read(by: receipt.readerNickname, at: receipt.timestamp))
|
||||||
}
|
}
|
||||||
|
|
||||||
func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {
|
func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {
|
||||||
updateMessageDeliveryStatus(messageID, status: status)
|
updateMessageDeliveryStatus(messageID, status: status)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {
|
private func updateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {
|
||||||
|
deliveryTracking.updateStatus(
|
||||||
// Helper function to check if we should skip this update
|
messageID: messageID,
|
||||||
func shouldSkipUpdate(currentStatus: DeliveryStatus?, newStatus: DeliveryStatus) -> Bool {
|
status: status,
|
||||||
guard let current = currentStatus else { return false }
|
messages: &messages,
|
||||||
|
privateChats: &privateChats,
|
||||||
// Don't downgrade from read to delivered
|
notifyChange: { [weak self] in
|
||||||
switch (current, newStatus) {
|
self?.objectWillChange.send()
|
||||||
case (.read, .delivered):
|
|
||||||
return true
|
|
||||||
case (.read, .sent):
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
messages.append(systemMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4158,12 +4131,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
/// If mesh is currently active, also append to the visible `messages`.
|
/// If mesh is currently active, also append to the visible `messages`.
|
||||||
@MainActor
|
@MainActor
|
||||||
private func addMeshOnlySystemMessage(_ content: String) {
|
private func addMeshOnlySystemMessage(_ content: String) {
|
||||||
let systemMessage = BitchatMessage(
|
let systemMessage = systemMessaging.createSystemMessage(content: content)
|
||||||
sender: "system",
|
|
||||||
content: content,
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
// Persist to mesh timeline
|
// Persist to mesh timeline
|
||||||
meshTimeline.append(systemMessage)
|
meshTimeline.append(systemMessage)
|
||||||
trimMeshTimelineIfNeeded()
|
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.
|
/// Also persists the message into the active channel's backing store so it survives timeline rebinds.
|
||||||
@MainActor
|
@MainActor
|
||||||
func addPublicSystemMessage(_ content: String) {
|
func addPublicSystemMessage(_ content: String) {
|
||||||
let systemMessage = BitchatMessage(
|
let systemMessage = systemMessaging.createSystemMessage(content: content)
|
||||||
sender: "system",
|
|
||||||
content: content,
|
|
||||||
timestamp: Date(),
|
|
||||||
isRelay: false
|
|
||||||
)
|
|
||||||
// Append to current visible messages
|
// Append to current visible messages
|
||||||
messages.append(systemMessage)
|
messages.append(systemMessage)
|
||||||
// Persist into the backing store for the active channel to survive rebinds
|
// Persist into the backing store for the active channel to survive rebinds
|
||||||
|
|||||||
Reference in New Issue
Block a user