From edd82a767e0f871226694bfb6b608214b8564618 Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:02:33 +0000 Subject: [PATCH] ios: fix chat list subscription status indicator stuck at empty "%" The toolbar-hosted indicator held its subscription totals in @State and refreshed them via a 1s poll; the toolbar rebuilds frequently, recreating the view and resetting the @State totals to the empty "share unknown" state. Move the totals into a dedicated SubsStatusModel observable (a shared singleton, like ChannelRelaysModel/ConnectProgressManager) so they survive view recreation, instead of binding poll state into the widely-observed ChatModel. The poll publishes only on change (SMPServerSubs is now Equatable), so the indicator does not re-render every idle second. --- apps/ios/Shared/Model/AppAPITypes.swift | 2 +- apps/ios/Shared/Model/ChatModel.swift | 18 ++++++++++++++++++ .../Shared/Views/ChatList/ChatListView.swift | 11 +++++------ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index a5a56174b1..b81751ad7f 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -2382,7 +2382,7 @@ struct ServerSessions: Codable { var hasSess: Bool { ssConnected > 0 } } -struct SMPServerSubs: Codable { +struct SMPServerSubs: Codable, Equatable { var ssActive: Int var ssPending: Int diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index a1d28b8e22..bbb64936dd 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -359,6 +359,24 @@ class ChannelRelaysModel: ObservableObject { } } +// Subscription totals for the chat list status indicator. Kept in a dedicated +// observable (not on ChatModel) so they survive the toolbar-hosted indicator +// being recreated on frequent toolbar rebuilds, without waking ChatModel's many +// observers on every poll tick. +class SubsStatusModel: ObservableObject { + static let shared = SubsStatusModel() + @Published var totalSubs: SMPServerSubs = SMPServerSubs.newSMPServerSubs + @Published var hasSession: Bool = false + + func update(_ totalSubs: SMPServerSubs, _ hasSession: Bool) { + // only publish on change - avoid re-rendering the indicator on every idle poll tick + if self.totalSubs != totalSubs || self.hasSession != hasSession { + self.totalSubs = totalSubs + self.hasSession = hasSession + } + } +} + // Spec: spec/state.md#ChatModel final class ChatModel: ObservableObject { @Published var onboardingStage: OnboardingStage? diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index d90149c7dd..7d6227a2ad 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -559,8 +559,8 @@ struct ChatListView: View { } struct SubsStatusIndicator: View { - @State private var subs: SMPServerSubs = SMPServerSubs.newSMPServerSubs - @State private var hasSess: Bool = false + // Totals live on a dedicated observable so they survive this view being recreated on frequent toolbar rebuilds + @ObservedObject private var subsModel = SubsStatusModel.shared @State private var task: Task? @State private var showServersSummary = false @@ -572,9 +572,9 @@ struct SubsStatusIndicator: View { } label: { HStack(spacing: 4) { Text("Chats").foregroundStyle(Color.primary).fixedSize().font(.headline) - SubscriptionStatusIndicatorView(subs: subs, hasSess: hasSess) + SubscriptionStatusIndicatorView(subs: subsModel.totalSubs, hasSess: subsModel.hasSession) if showSubscriptionPercentage { - SubscriptionStatusPercentageView(subs: subs, hasSess: hasSess) + SubscriptionStatusPercentageView(subs: subsModel.totalSubs, hasSess: subsModel.hasSession) } } } @@ -598,8 +598,7 @@ struct SubsStatusIndicator: View { do { let (subs, hasSess) = try await getAgentSubsTotal() await MainActor.run { - self.subs = subs - self.hasSess = hasSess + SubsStatusModel.shared.update(subs, hasSess) } } catch let error { logger.error("getSubsTotal error: \(responseError(error))")