diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 65476fd93f..3ffbe9a00d 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1404,6 +1404,14 @@ func apiGetVersion() throws -> CoreVersionInfo { throw r } +func getAgentSubsTotal() throws -> (SMPServerSubs, Bool) { + let userId = try currentUserId("getAgentSubsTotal") + let r = chatSendCmdSync(.getAgentSubsTotal(userId: userId), log: false) + if case let .agentSubsTotal(_, subsTotal, hasSession) = r { return (subsTotal, hasSession) } + logger.error("getAgentSubsTotal error: \(String(describing: r))") + throw r +} + func getAgentServersSummary() throws -> PresentedServersSummary { let userId = try currentUserId("getAgentServersSummary") let r = chatSendCmdSync(.getAgentServersSummary(userId: userId), log: false) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 16f87f0c13..69dcf7501a 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -266,9 +266,9 @@ struct ChatListView: View { } struct SubsStatusIndicator: View { - @State private var serversSummary: PresentedServersSummary? + @State private var subs: SMPServerSubs = SMPServerSubs.newSMPServerSubs + @State private var hasSess: Bool = false @State private var timer: Timer? = nil - @State private var timerCounter = 0 @State private var showServersSummary = false @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false @@ -277,12 +277,10 @@ struct SubsStatusIndicator: View { Button { showServersSummary = true } label: { - let subs = serversSummary?.allUsersSMP.smpTotals.subs ?? SMPServerSubs.newSMPServerSubs - let sess = serversSummary?.allUsersSMP.smpTotals.sessions ?? ServerSessions.newServerSessions HStack(spacing: 4) { - SubscriptionStatusIndicatorView(subs: subs, sess: sess) + SubscriptionStatusIndicatorView(subs: subs, hasSess: hasSess) if showSubscriptionPercentage { - SubscriptionStatusPercentageView(subs: subs, sess: sess) + SubscriptionStatusPercentageView(subs: subs, hasSess: hasSess) } } } @@ -293,14 +291,14 @@ struct SubsStatusIndicator: View { stopTimer() } .sheet(isPresented: $showServersSummary) { - ServersSummaryView(serversSummary: $serversSummary) + ServersSummaryView() } } private func startTimer() { timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in if AppChatState.shared.value == .active { - getServersSummary() + getSubsTotal() } } } @@ -310,11 +308,11 @@ struct SubsStatusIndicator: View { timer = nil } - private func getServersSummary() { + private func getSubsTotal() { do { - serversSummary = try getAgentServersSummary() + (subs, hasSess) = try getAgentSubsTotal() } catch let error { - logger.error("getAgentServersSummary error: \(responseError(error))") + logger.error("getSubsTotal error: \(responseError(error))") } } } diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 3639dcd5e4..2e0dd9d9e4 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -12,11 +12,12 @@ import SimpleXChat struct ServersSummaryView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme - @Binding var serversSummary: PresentedServersSummary? + @State private var serversSummary: PresentedServersSummary? = nil @State private var selectedUserCategory: PresentedUserCategory = .allUsers @State private var selectedServerType: PresentedServerType = .smp @State private var selectedSMPServer: String? = nil @State private var selectedXFTPServer: String? = nil + @State private var timer: Timer? = nil @State private var alert: SomeAlert? @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false @@ -47,10 +48,36 @@ struct ServersSummaryView: View { if m.users.filter({ u in u.user.activeUser || !u.user.hidden }).count == 1 { selectedUserCategory = .currentUser } + getServersSummary() + startTimer() + } + .onDisappear { + stopTimer() } .alert(item: $alert) { $0.alert } } + private func startTimer() { + timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in + if AppChatState.shared.value == .active { + getServersSummary() + } + } + } + + private func getServersSummary() { + do { + serversSummary = try getAgentServersSummary() + } catch let error { + logger.error("getAgentServersSummary error: \(responseError(error))") + } + } + + private func stopTimer() { + timer?.invalidate() + timer = nil + } + private func shareButton() -> some View { Button { if let serversSummary = serversSummary { @@ -180,9 +207,9 @@ struct ServersSummaryView: View { } header: { HStack { Text("Message reception") - SubscriptionStatusIndicatorView(subs: totals.subs, sess: totals.sessions) + SubscriptionStatusIndicatorView(subs: totals.subs, hasSess: totals.sessions.hasSess) if showSubscriptionPercentage { - SubscriptionStatusPercentageView(subs: totals.subs, sess: totals.sessions) + SubscriptionStatusPercentageView(subs: totals.subs, hasSess: totals.sessions.hasSess) } } } @@ -260,9 +287,9 @@ struct ServersSummaryView: View { if let subs = srvSumm.subs { Spacer() if showSubscriptionPercentage { - SubscriptionStatusPercentageView(subs: subs, sess: srvSumm.sessionsOrNew) + SubscriptionStatusPercentageView(subs: subs, hasSess: srvSumm.sessionsOrNew.hasSess) } - SubscriptionStatusIndicatorView(subs: subs, sess: srvSumm.sessionsOrNew) + SubscriptionStatusIndicatorView(subs: subs, hasSess: srvSumm.sessionsOrNew.hasSess) } else if let sess = srvSumm.sessions { Spacer() Image(systemName: "arrow.up.circle") @@ -356,6 +383,7 @@ struct ServersSummaryView: View { Task { do { try await resetAgentServersStats() + getServersSummary() } catch let error { alert = SomeAlert( alert: mkAlert( @@ -380,11 +408,11 @@ struct ServersSummaryView: View { struct SubscriptionStatusIndicatorView: View { @EnvironmentObject var m: ChatModel var subs: SMPServerSubs - var sess: ServerSessions + var hasSess: Bool var body: some View { let onionHosts = networkUseOnionHostsGroupDefault.get() - let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, sess) + let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess) if #available(iOS 16.0, *) { Image(systemName: "dot.radiowaves.up.forward", variableValue: variableValue) .foregroundColor(color) @@ -398,18 +426,18 @@ struct SubscriptionStatusIndicatorView: View { struct SubscriptionStatusPercentageView: View { @EnvironmentObject var m: ChatModel var subs: SMPServerSubs - var sess: ServerSessions + var hasSess: Bool var body: some View { let onionHosts = networkUseOnionHostsGroupDefault.get() - let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, sess) + let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess) Text(verbatim: "\(Int(floor(statusPercent * 100)))%") .foregroundColor(.secondary) .font(.caption) } } -func subscriptionStatusColorAndPercentage(_ online: Bool, _ onionHosts: OnionHosts, _ subs: SMPServerSubs, _ sess: ServerSessions) -> (Color, Double, Double, Double) { +func subscriptionStatusColorAndPercentage(_ online: Bool, _ onionHosts: OnionHosts, _ subs: SMPServerSubs, _ hasSess: Bool) -> (Color, Double, Double, Double) { func roundedToQuarter(_ n: Double) -> Double { n >= 1 ? 1 : n <= 0 ? 0 @@ -424,12 +452,12 @@ func subscriptionStatusColorAndPercentage(_ online: Bool, _ onionHosts: OnionHos ? ( subs.ssActive == 0 ? ( - sess.ssConnected == 0 ? noConnColorAndPercent : (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) + hasSess ? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) : noConnColorAndPercent ) : ( // ssActive > 0 - sess.ssConnected == 0 - ? (.orange, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) // This would mean implementation error - : (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) + hasSess + ? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) + : (.orange, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) // This would mean implementation error ) ) : noConnColorAndPercent @@ -482,9 +510,9 @@ struct SMPServerSummaryView: View { } header: { HStack { Text("Message reception") - SubscriptionStatusIndicatorView(subs: subs, sess: summary.sessionsOrNew) + SubscriptionStatusIndicatorView(subs: subs, hasSess: summary.sessionsOrNew.hasSess) if showSubscriptionPercentage { - SubscriptionStatusPercentageView(subs: subs, sess: summary.sessionsOrNew) + SubscriptionStatusPercentageView(subs: subs, hasSess: summary.sessionsOrNew.hasSess) } } } @@ -716,7 +744,5 @@ struct DetailedXFTPStatsView: View { } #Preview { - ServersSummaryView( - serversSummary: Binding.constant(nil) - ) + ServersSummaryView() } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 443b2d0502..8ed3d63e53 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -146,6 +146,7 @@ public enum ChatCommand { case apiStandaloneFileInfo(url: String) // misc case showVersion + case getAgentSubsTotal(userId: Int64) case getAgentServersSummary(userId: Int64) case resetAgentServersStats case string(String) @@ -309,6 +310,7 @@ public enum ChatCommand { case let .apiDownloadStandaloneFile(userId, link, file): return "/_download \(userId) \(link) \(file.filePath)" case let .apiStandaloneFileInfo(link): return "/_download info \(link)" case .showVersion: return "/version" + case let .getAgentSubsTotal(userId): return "/get subs total \(userId)" case let .getAgentServersSummary(userId): return "/get servers summary \(userId)" case .resetAgentServersStats: return "/reset servers stats" case let .string(str): return str @@ -447,6 +449,7 @@ public enum ChatCommand { case .apiDownloadStandaloneFile: return "apiDownloadStandaloneFile" case .apiStandaloneFileInfo: return "apiStandaloneFileInfo" case .showVersion: return "showVersion" + case .getAgentSubsTotal: return "getAgentSubsTotal" case .getAgentServersSummary: return "getAgentServersSummary" case .resetAgentServersStats: return "resetAgentServersStats" case .string: return "console command" @@ -678,6 +681,7 @@ public enum ChatResponse: Decodable, Error { // misc case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration]) case cmdOk(user: UserRef?) + case agentSubsTotal(user: UserRef, subsTotal: SMPServerSubs, hasSession: Bool) case agentServersSummary(user: UserRef, serversSummary: PresentedServersSummary) case agentSubsSummary(user: UserRef, subsSummary: SMPServerSubs) case chatCmdError(user_: UserRef?, chatError: ChatError) @@ -839,6 +843,7 @@ public enum ChatResponse: Decodable, Error { case .contactPQEnabled: return "contactPQEnabled" case .versionInfo: return "versionInfo" case .cmdOk: return "cmdOk" + case .agentSubsTotal: return "agentSubsTotal" case .agentServersSummary: return "agentServersSummary" case .agentSubsSummary: return "agentSubsSummary" case .chatCmdError: return "chatCmdError" @@ -1005,6 +1010,7 @@ public enum ChatResponse: Decodable, Error { case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)") case let .versionInfo(versionInfo, chatMigrations, agentMigrations): return "\(String(describing: versionInfo))\n\nchat migrations: \(chatMigrations.map(\.upName))\n\nagent migrations: \(agentMigrations.map(\.upName))" case .cmdOk: return noDetails + case let .agentSubsTotal(u, subsTotal, hasSession): return withUser(u, "subsTotal: \(String(describing: subsTotal))\nhasSession: \(hasSession)") case let .agentServersSummary(u, serversSummary): return withUser(u, String(describing: serversSummary)) case let .agentSubsSummary(u, subsSummary): return withUser(u, String(describing: subsSummary)) case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) @@ -2324,6 +2330,8 @@ public struct ServerSessions: Codable { ssErrors: 0, ssConnecting: 0 ) + + public var hasSess: Bool { ssConnected > 0 } } public struct SMPServerSubs: Codable {