diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 430d77a0fc..db1d2989c0 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -264,7 +264,8 @@ struct ChatListView: View { } struct SubsStatusIndicator: View { - @State private var subs: SMPServerSubs = SMPServerSubs(ssActive: 0, ssPending: 0) + @State private var subs: SMPServerSubs = SMPServerSubs.newSMPServerSubs + @State private var sess: ServerSessions = ServerSessions.newServerSessions @State private var timer: Timer? = nil @State private var timerCounter = 0 @State private var showServersSummary = false @@ -278,7 +279,7 @@ struct SubsStatusIndicator: View { Button { showServersSummary = true } label: { - SubscriptionStatusView(subs: subs) + SubscriptionStatusView(subs: subs, sess: sess) } .onAppear { startInitialTimer() @@ -293,7 +294,7 @@ struct SubsStatusIndicator: View { private func startInitialTimer() { timer = Timer.scheduledTimer(withTimeInterval: initialInterval, repeats: true) { _ in - getSubsSummary() + getServersSummary() timerCounter += 1 // Switch to the regular timer after the initial phase if timerCounter * Int(initialInterval) >= Int(initialPhaseDuration) { @@ -305,7 +306,7 @@ struct SubsStatusIndicator: View { func switchToRegularTimer() { timer?.invalidate() timer = Timer.scheduledTimer(withTimeInterval: regularInterval, repeats: true) { _ in - getSubsSummary() + getServersSummary() } } @@ -314,11 +315,12 @@ struct SubsStatusIndicator: View { timer = nil } - private func getSubsSummary() { + private func getServersSummary() { do { - subs = try getAgentSubsSummary() + let summ = try getAgentServersSummary() + (subs, sess) = (summ.allUsersSMP.smpTotals.subs, summ.allUsersSMP.smpTotals.sessions) } catch let error { - logger.error("getAgentSubsSummary error: \(responseError(error))") + logger.error("getAgentServersSummary error: \(responseError(error))") } } } diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 7b703544e2..55998fe458 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -207,10 +207,10 @@ struct ServersSummaryView: View { } } - private func smpServerView(_ server: SMPServerSummary, _ showReconnectButton: Bool, _ statsStartedAt: Date) -> some View { - NavigationLink(tag: server.id, selection: $selectedSMPServer) { + private func smpServerView(_ srvSumm: SMPServerSummary, _ showReconnectButton: Bool, _ statsStartedAt: Date) -> some View { + NavigationLink(tag: srvSumm.id, selection: $selectedSMPServer) { SMPServerSummaryView( - summary: server, + summary: srvSumm, showReconnectButton: showReconnectButton, statsStartedAt: statsStartedAt ) @@ -218,12 +218,15 @@ struct ServersSummaryView: View { .navigationBarTitleDisplayMode(.large) } label: { HStack { - if let subs = server.subs { - SubscriptionStatusView(subs: subs) - .frame(width: 16, alignment: .center) - .padding(.trailing, 4) + if srvSumm.connected { + SubscriptionStatusView( + subs: srvSumm.subsOrNew, + sess: srvSumm.sessionsOrNew + ) + .frame(width: 16, alignment: .center) + .padding(.trailing, 4) } - Text(serverAddress(server.smpServer)) + Text(serverAddress(srvSumm.smpServer)) .lineLimit(1) } } @@ -255,16 +258,16 @@ struct ServersSummaryView: View { } } - private func xftpServerView(_ server: XFTPServerSummary, _ statsStartedAt: Date) -> some View { - NavigationLink(tag: server.id, selection: $selectedXFTPServer) { + private func xftpServerView(_ srvSumm: XFTPServerSummary, _ statsStartedAt: Date) -> some View { + NavigationLink(tag: srvSumm.id, selection: $selectedXFTPServer) { XFTPServerSummaryView( - summary: server, + summary: srvSumm, statsStartedAt: statsStartedAt ) .navigationBarTitle("XFTP server") .navigationBarTitleDisplayMode(.large) } label: { - Text(serverAddress(server.xftpServer)) + Text(serverAddress(srvSumm.xftpServer)) .lineLimit(1) } } @@ -314,43 +317,50 @@ struct ServersSummaryView: View { struct SubscriptionStatusView: View { @EnvironmentObject var m: ChatModel var subs: SMPServerSubs + var sess: ServerSessions var body: some View { - let netInfo = m.networkInfo - if netInfo.online { - let (color, variableValue, opacity) = onlineIconColor - if #available(iOS 16.0, *) { - Image(systemName: "dot.radiowaves.up.forward", variableValue: variableValue) - .foregroundColor(color) - } else { - Image(systemName: "dot.radiowaves.up.forward") - .foregroundColor(color.opacity(opacity)) - } + let (color, variableValue, opacity) = iconColor + if #available(iOS 16.0, *) { + Image(systemName: "dot.radiowaves.up.forward", variableValue: variableValue) + .foregroundColor(color) } else { Image(systemName: "dot.radiowaves.up.forward") - .foregroundColor(Color(uiColor: .tertiaryLabel)) + .foregroundColor(color.opacity(opacity)) } } - var onlineIconColor: (Color, Double, Double) { - if subs.ssActive > 0 { - let variableValue = roundToQuarter() - return (.accentColor, variableValue, activeSubsPercentage) - } else { - return (Color(uiColor: .tertiaryLabel), 1, 1) - } + private var iconColor: (Color, Double, Double) { + m.networkInfo.online && (subs.total > 0 || sess.total > 0) + ? ( // Status to be displayed based on subs + subs.total > 0 + ? ( + subs.ssActive == 0 + ? ( + sess.ssConnected == 0 ? noConnColor : (.accentColor, activeSubsRounded, subs.shareOfActive) + ) + : ( // ssActive > 0 + sess.ssConnected == 0 + ? (.orange, activeSubsRounded, subs.shareOfActive) // This would mean implementation error + : (.accentColor, activeSubsRounded, subs.shareOfActive) + ) + ) + // subs.total == 0 and sess.total > 0; Status to be displayed based on sessions + : (.accentColor, connectedSessRounded, sess.shareOfConnected) + ) + : noConnColor } - func roundToQuarter() -> Double { - activeSubsPercentage >= 1 ? 1 - : activeSubsPercentage <= 0 ? 0 - : (activeSubsPercentage * 4).rounded() / 4 - } + private var noConnColor: (Color, Double, Double) { (Color(uiColor: .tertiaryLabel), 1, 1) } - var activeSubsPercentage: Double { - let total = subs.ssActive + subs.ssPending - guard total != 0 else { return 0.0 } - return Double(subs.ssActive) / Double(total) + private var activeSubsRounded: Double { roundedToQuarter(subs.shareOfActive) } + + private var connectedSessRounded: Double { roundedToQuarter(sess.shareOfConnected) } + + private func roundedToQuarter(_ n: Double) -> Double { + n >= 1 ? 1 + : n <= 0 ? 0 + : (n * 4).rounded() / 4 } } @@ -380,8 +390,17 @@ struct SMPServerSummaryView: View { } } - if showReconnectButton { - reconnectButtonSection() + if summary.connected && showReconnectButton { + Section { + connectionStatusRow() + reconnectButtonSection() + } footer: { + if summary.subs != nil { + Text("Connection status is displayed based on Message subscriptions.") + } else if summary.sessions != nil { + Text("Connection status is displayed based on Transport sessions.") + } + } } if let subs = summary.subs { @@ -399,35 +418,44 @@ struct SMPServerSummaryView: View { .alert(item: $alert) { $0.alert } } + private func connectionStatusRow() -> some View { + HStack { + Text("Connection status") + Spacer() + SubscriptionStatusView( + subs: summary.subsOrNew, + sess: summary.sessionsOrNew + ) + } + } + private func reconnectButtonSection() -> some View { - Section { - Button { - alert = SomeAlert( - alert: Alert( - title: Text("Reconnect server?"), - message: Text("Reconnect server to force message delivery. It uses additional traffic."), - primaryButton: .default(Text("Ok")) { - Task { - do { - try await reconnectServer(smpServer: summary.smpServer) - } catch let error { - alert = SomeAlert( - alert: mkAlert( - title: "Error reconnecting server", - message: "\(responseError(error))" - ), - id: "error reconnecting server" - ) - } + Button { + alert = SomeAlert( + alert: Alert( + title: Text("Reconnect server?"), + message: Text("Reconnect server to force message delivery. It uses additional traffic."), + primaryButton: .default(Text("Ok")) { + Task { + do { + try await reconnectServer(smpServer: summary.smpServer) + } catch let error { + alert = SomeAlert( + alert: mkAlert( + title: "Error reconnecting server", + message: "\(responseError(error))" + ), + id: "error reconnecting server" + ) } - }, - secondaryButton: .cancel() - ), - id: "reconnect server question" - ) - } label: { - Text("Reconnect") - } + } + }, + secondaryButton: .cancel() + ), + id: "reconnect server question" + ) + } label: { + Text("Reconnect") } } } @@ -437,15 +465,12 @@ struct SMPSubsView: View { var showPending: Bool var body: some View { - Section { + Section("Message subscriptions") { infoRow("Active", "\(subs.ssActive)") - infoRow("Pending", "\(subs.ssPending)") - infoRow("Total", "\(subs.ssActive + subs.ssPending)") - } header: { - HStack { - Text("Message subscriptions") - SubscriptionStatusView(subs: subs) + if showPending { + infoRow("Pending", "\(subs.ssPending)") } + infoRow("Total", "\(subs.total)") } } } @@ -458,6 +483,7 @@ struct ServerSessionsView: View { infoRow("Connected", "\(sess.ssConnected)") infoRow("Errors", "\(sess.ssErrors)") infoRow("Connecting", "\(sess.ssConnecting)") + infoRow("Total", "\(sess.total)") } } } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 4394a11778..5f6d0f0943 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -2281,12 +2281,35 @@ public struct SMPServerSummary: Codable, Identifiable { public var id: String { smpServer } public var connected: Bool { sessions != nil || subs != nil } + + public var sessionsOrNew: ServerSessions { sessions ?? ServerSessions.newServerSessions } + + public var subsOrNew: SMPServerSubs { subs ?? SMPServerSubs.newSMPServerSubs } } public struct ServerSessions: Codable { public var ssConnected: Int public var ssErrors: Int public var ssConnecting: Int + + public init(ssConnected: Int, ssErrors: Int, ssConnecting: Int) { + self.ssConnected = ssConnected + self.ssErrors = ssErrors + self.ssConnecting = ssConnecting + } + + static public var newServerSessions = ServerSessions( + ssConnected: 0, + ssErrors: 0, + ssConnecting: 0 + ) + + public var total: Int { ssConnected + ssErrors + ssConnecting } + + public var shareOfConnected: Double { + guard total != 0 else { return 0.0 } + return Double(ssConnected) / Double(total) + } } public struct SMPServerSubs: Codable { @@ -2297,6 +2320,18 @@ public struct SMPServerSubs: Codable { self.ssActive = ssActive self.ssPending = ssPending } + + static public var newSMPServerSubs = SMPServerSubs( + ssActive: 0, + ssPending: 0 + ) + + public var total: Int { ssActive + ssPending } + + public var shareOfActive: Double { + guard total != 0 else { return 0.0 } + return Double(ssActive) / Double(total) + } } public struct AgentSMPServerStatsData: Codable {