From c3f67aff6919f27e98cac525c23e06713c22dabf Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Tue, 13 Aug 2024 23:08:04 +0300 Subject: [PATCH] ios: speed up network status handling (#4678) * move network status into a separate model * reduce network model observation scope (#4679) * dont pass chat --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/ChatModel.swift | 39 +++++++++++-------- apps/ios/Shared/Model/SimpleXAPI.swift | 20 +++++----- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 7 ++-- .../Chat/Group/GroupMemberInfoView.swift | 2 +- .../Views/ChatList/ChatPreviewView.swift | 36 +++++++++++------ 5 files changed, 64 insertions(+), 40 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index d330e154b6..347ffa7a8f 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -100,6 +100,29 @@ class ItemsModel: ObservableObject { } } +class NetworkModel: ObservableObject { + // map of connections network statuses, key is agent connection id + @Published var networkStatuses: Dictionary = [:] + + static let shared = NetworkModel() + + private init() { } + + func setContactNetworkStatus(_ contact: Contact, _ status: NetworkStatus) { + if let conn = contact.activeConn { + networkStatuses[conn.agentConnId] = status + } + } + + func contactNetworkStatus(_ contact: Contact) -> NetworkStatus { + if let conn = contact.activeConn { + networkStatuses[conn.agentConnId] ?? .unknown + } else { + .unknown + } + } +} + final class ChatModel: ObservableObject { @Published var onboardingStage: OnboardingStage? @Published var setDeliveryReceipts = false @@ -122,8 +145,6 @@ final class ChatModel: ObservableObject { // list of chat "previews" @Published var chats: [Chat] = [] @Published var deletedChats: Set = [] - // map of connections network statuses, key is agent connection id - @Published var networkStatuses: Dictionary = [:] // current chat @Published var chatId: String? var chatItemStatuses: Dictionary = [:] @@ -909,20 +930,6 @@ final class ChatModel: ObservableObject { while i < maxIx && inView(i) { i += 1 } return im.reversedChatItems[min(i - 1, maxIx)] } - - func setContactNetworkStatus(_ contact: Contact, _ status: NetworkStatus) { - if let conn = contact.activeConn { - networkStatuses[conn.agentConnId] = status - } - } - - func contactNetworkStatus(_ contact: Contact) -> NetworkStatus { - if let conn = contact.activeConn { - networkStatuses[conn.agentConnId] ?? .unknown - } else { - .unknown - } - } } struct ShowingInvitation { diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 2f1abe44ce..76d043b2aa 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1167,7 +1167,7 @@ func acceptContactRequest(incognito: Bool, contactRequest: UserContactRequest) a let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: []) await MainActor.run { ChatModel.shared.replaceChat(contactRequest.id, chat) - ChatModel.shared.setContactNetworkStatus(contact, .connected) + NetworkModel.shared.setContactNetworkStatus(contact, .connected) } if contact.sndReady { DispatchQueue.main.async { @@ -1659,6 +1659,7 @@ func processReceivedMsg(_ res: ChatResponse) async { await TerminalItems.shared.add(.resp(.now, res)) } let m = ChatModel.shared + let n = NetworkModel.shared logger.debug("processReceivedMsg: \(res.responseType)") switch res { case let .contactDeletedByContact(user, contact): @@ -1681,7 +1682,7 @@ func processReceivedMsg(_ res: ChatResponse) async { NtfManager.shared.notifyContactConnected(user, contact) } await MainActor.run { - m.setContactNetworkStatus(contact, .connected) + n.setContactNetworkStatus(contact, .connected) } case let .contactConnecting(user, contact): if active(user) && contact.directOrUsed { @@ -1704,7 +1705,7 @@ func processReceivedMsg(_ res: ChatResponse) async { } } await MainActor.run { - m.setContactNetworkStatus(contact, .connected) + n.setContactNetworkStatus(contact, .connected) } case let .receivedContactRequest(user, contactRequest): if active(user) { @@ -1746,27 +1747,27 @@ func processReceivedMsg(_ res: ChatResponse) async { case let .networkStatus(status, connections): // dispatch queue to synchronize access networkStatusesLock.sync { - var ns = m.networkStatuses + var ns = n.networkStatuses // slow loop is on the background thread for cId in connections { ns[cId] = status } // fast model update is on the main thread DispatchQueue.main.sync { - m.networkStatuses = ns + n.networkStatuses = ns } } case let .networkStatuses(_, statuses): () // dispatch queue to synchronize access networkStatusesLock.sync { - var ns = m.networkStatuses + var ns = n.networkStatuses // slow loop is on the background thread for s in statuses { ns[s.agentConnId] = s.networkStatus } // fast model update is on the main thread DispatchQueue.main.sync { - m.networkStatuses = ns + n.networkStatuses = ns } } case let .newChatItem(user, aChatItem): @@ -1907,7 +1908,7 @@ func processReceivedMsg(_ res: ChatResponse) async { } if let contact = memberContact { await MainActor.run { - m.setContactNetworkStatus(contact, .connected) + n.setContactNetworkStatus(contact, .connected) } } case let .groupUpdated(user, toGroup): @@ -2132,12 +2133,13 @@ func processReceivedMsg(_ res: ChatResponse) async { func switchToLocalSession() { let m = ChatModel.shared + let n = NetworkModel.shared m.remoteCtrlSession = nil do { m.users = try listUsers() try getUserChatData() let statuses = (try apiGetNetworkStatuses()).map { s in (s.agentConnId, s.networkStatus) } - m.networkStatuses = Dictionary(uniqueKeysWithValues: statuses) + n.networkStatuses = Dictionary(uniqueKeysWithValues: statuses) } catch let error { logger.debug("error updating chat data: \(responseError(error))") } diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 0986b58d9c..ff46b9619c 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -92,6 +92,7 @@ struct ChatInfoView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme @Environment(\.dismiss) var dismiss: DismissAction + @ObservedObject var networkModel = NetworkModel.shared @ObservedObject var chat: Chat @State var contact: Contact @State var localAlias: String @@ -502,14 +503,14 @@ struct ChatInfoView: View { .foregroundColor(theme.colors.primary) .font(.system(size: 14)) Spacer() - Text(chatModel.contactNetworkStatus(contact).statusString) + Text(networkModel.contactNetworkStatus(contact).statusString) .foregroundColor(theme.colors.secondary) serverImage() } } private func serverImage() -> some View { - let status = chatModel.contactNetworkStatus(contact) + let status = networkModel.contactNetworkStatus(contact) return Image(systemName: status.imageName) .foregroundColor(status == .connected ? .green : theme.colors.secondary) .font(.system(size: 12)) @@ -557,7 +558,7 @@ struct ChatInfoView: View { private func networkStatusAlert() -> Alert { Alert( title: Text("Network status"), - message: Text(chatModel.contactNetworkStatus(contact).statusExplanation) + message: Text(networkModel.contactNetworkStatus(contact).statusExplanation) ) } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 436099261b..ddf3b8e4b9 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -355,7 +355,7 @@ struct GroupMemberInfoView: View { ItemsModel.shared.loadOpenChat(memberContact.id) { dismissAllSheets(animated: true) } - chatModel.setContactNetworkStatus(memberContact, .connected) + NetworkModel.shared.setContactNetworkStatus(memberContact, .connected) } } catch let error { logger.error("createMemberContactButton apiCreateMemberContact error: \(responseError(error))") diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index a6000e90f1..9e6d3005b6 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -376,17 +376,7 @@ struct ChatPreviewView: View { switch chat.chatInfo { case let .direct(contact): if contact.active && contact.activeConn != nil { - switch (chatModel.contactNetworkStatus(contact)) { - case .connected: incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size) - case .error: - Image(systemName: "exclamationmark.circle") - .resizable() - .scaledToFit() - .frame(width: dynamicChatInfoSize, height: dynamicChatInfoSize) - .foregroundColor(theme.colors.secondary) - default: - ProgressView() - } + NetworkStatusView(contact: contact, size: size) } else { incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size) } @@ -400,6 +390,30 @@ struct ChatPreviewView: View { incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size) } } + + struct NetworkStatusView: View { + @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize + @EnvironmentObject var theme: AppTheme + @ObservedObject var networkModel = NetworkModel.shared + + let contact: Contact + let size: CGFloat + + var body: some View { + let dynamicChatInfoSize = dynamicSize(userFont).chatInfoSize + switch (networkModel.contactNetworkStatus(contact)) { + case .connected: incognitoIcon(contact.contactConnIncognito, theme.colors.secondary, size: size) + case .error: + Image(systemName: "exclamationmark.circle") + .resizable() + .scaledToFit() + .frame(width: dynamicChatInfoSize, height: dynamicChatInfoSize) + .foregroundColor(theme.colors.secondary) + default: + ProgressView() + } + } + } } @ViewBuilder func incognitoIcon(_ incognito: Bool, _ secondaryColor: Color, size: CGFloat) -> some View {