core, ui: replace map of network statuses with subscription status of current chat (#6353)

* core: subscription status wip

* update

* update

* update

* remove statuses core

* cleanup ios

* comment

* plans

* remove NetworkStatus

* ios wip

* contact sub status

* Revert "contact sub status"

This reverts commit 50cf94beed.

* sub status

* set on connected

* kotlin

* rename

* layout

* member status

* kotlin

* fix chat subscription status

* string

* core: update simplexmq

* client notices

* update simplexmq

* update alert

* update simplexmq

* android/desktop

* formatting

* fix tests

* update plans and API docs

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
spaced4ndy
2025-10-18 21:53:47 +00:00
committed by GitHub
parent 0dc33708a3
commit a65151ba6d
74 changed files with 529 additions and 527 deletions
+39 -49
View File
@@ -15,8 +15,6 @@ import SwiftUI
private var chatController: chat_ctrl?
private let networkStatusesLock = DispatchQueue(label: "chat.simplex.app.network-statuses.lock")
enum TerminalItem: Identifiable {
case cmd(Date, ChatCommand)
case res(Date, ChatAPIResult)
@@ -1319,6 +1317,10 @@ func apiCreateUserAddress() async throws -> CreatedConnLink? {
let userId = try currentUserId("apiCreateUserAddress")
let r: APIResult<ChatResponse1>? = await chatApiSendCmdWithRetry(.apiCreateMyAddress(userId: userId))
if case let .result(.userContactLinkCreated(_, connLink)) = r { return connLink }
if case let .error(.errorAgent(.NOTICE(server, preset, expires))) = r {
showClientNotice(server, preset, expires)
return nil
}
if let r { throw r.unexpected } else { return nil }
}
@@ -1640,7 +1642,6 @@ func acceptContactRequest(incognito: Bool, contactRequestId: Int64, inProgress:
} else {
ChatModel.shared.replaceChat(contactRequestChatId(contactRequestId), chat)
}
NetworkModel.shared.setContactNetworkStatus(contact, .connected)
inProgress?.wrappedValue = false
}
if contact.sndReady {
@@ -1728,12 +1729,6 @@ func apiCallStatus(_ contact: Contact, _ status: String) async throws {
}
}
func apiGetNetworkStatuses() throws -> [ConnNetworkStatus] {
let r: ChatResponse1 = try chatSendCmdSync(.apiGetNetworkStatuses)
if case let .networkStatuses(_, statuses) = r { return statuses }
throw r.unexpected
}
func markChatRead(_ im: ItemsModel, _ chat: Chat) async {
do {
if chat.chatStats.unreadCount > 0 {
@@ -1899,6 +1894,10 @@ func apiUpdateGroup(_ groupId: Int64, _ groupProfile: GroupProfile) async throws
func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> GroupLink? {
let r: APIResult<ChatResponse2>? = await chatApiSendCmdWithRetry(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole))
if case let .result(.groupLinkCreated(_, _, groupLink)) = r { return groupLink }
if case let .error(.errorAgent(.NOTICE(server, preset, expires))) = r {
showClientNotice(server, preset, expires)
return nil
}
if let r { throw r.unexpected } else { return nil }
}
@@ -1955,7 +1954,6 @@ func acceptMemberContact(contactId: Int64, inProgress: Binding<Bool>? = nil) asy
if let contact = await apiAcceptMemberContact(contactId: contactId) {
await MainActor.run {
ChatModel.shared.updateContact(contact)
NetworkModel.shared.setContactNetworkStatus(contact, .connected)
inProgress?.wrappedValue = false
}
if contact.sndReady {
@@ -2241,7 +2239,6 @@ class ChatReceiver {
func processReceivedMsg(_ res: ChatEvent) async {
let m = ChatModel.shared
let n = NetworkModel.shared
logger.debug("processReceivedMsg: \(res.responseType)")
switch res {
case let .contactDeletedByContact(user, contact):
@@ -2258,14 +2255,15 @@ func processReceivedMsg(_ res: ChatEvent) async {
m.dismissConnReqView(conn.id)
m.removeChat(conn.id)
}
if contact.id == m.chatId, let conn = contact.activeConn {
m.chatAgentConnId = conn.agentConnId
m.chatSubStatus = .active
}
}
}
if contact.directOrUsed {
NtfManager.shared.notifyContactConnected(user, contact)
}
await MainActor.run {
n.setContactNetworkStatus(contact, .connected)
}
case let .contactConnecting(user, contact):
if active(user) && contact.directOrUsed {
await MainActor.run {
@@ -2286,9 +2284,6 @@ func processReceivedMsg(_ res: ChatEvent) async {
}
}
}
await MainActor.run {
n.setContactNetworkStatus(contact, .connected)
}
case let .receivedContactRequest(user, contactRequest, chat_):
if active(user) {
await MainActor.run {
@@ -2327,30 +2322,10 @@ func processReceivedMsg(_ res: ChatEvent) async {
_ = m.upsertGroupMember(groupInfo, toMember)
}
}
case let .networkStatus(status, connections):
// dispatch queue to synchronize access
networkStatusesLock.sync {
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 {
n.networkStatuses = ns
}
}
case let .networkStatuses(_, statuses): ()
// dispatch queue to synchronize access
networkStatusesLock.sync {
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 {
n.networkStatuses = ns
case let .subscriptionStatus(status, connections):
if let chatAgentConnId = m.chatAgentConnId, connections.contains(chatAgentConnId) {
await MainActor.run {
m.chatSubStatus = status
}
}
case let .chatInfoUpdated(user, chatInfo):
@@ -2554,11 +2529,6 @@ func processReceivedMsg(_ res: ChatEvent) async {
_ = m.upsertGroupMember(groupInfo, member)
}
}
if let contact = memberContact {
await MainActor.run {
n.setContactNetworkStatus(contact, .connected)
}
}
case let .groupUpdated(user, toGroup):
if active(user) {
await MainActor.run {
@@ -2784,13 +2754,10 @@ func processReceivedMsg(_ res: ChatEvent) 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) }
n.networkStatuses = Dictionary(uniqueKeysWithValues: statuses)
} catch let error {
logger.debug("error updating chat data: \(responseError(error))")
}
@@ -2898,3 +2865,26 @@ private struct UserResponse: Decodable {
var user: User?
var error: String?
}
private func showClientNotice(_ server: String, _ preset: Bool, _ expiresAt: Date?) {
DispatchQueue.main.async {
var message = "Server: \(server).\nConditions of use violation notice received from \(preset ? "preset" : "this") server.\nNo IDs shared, see How it works."
if let expiresAt {
message += "\n\nNew addresses can be created after \(expiresAt.formatted(date: .abbreviated, time: .shortened))."
}
showAlert("Not allowed", message: message) {
let howItWorks = UIAlertAction(title: NSLocalizedString("How it works", comment: "alert button"), style: .default, handler: { _ in
UIApplication.shared.open(contentModerationPostLink)
})
return preset
? [
okAlertAction,
UIAlertAction(title: NSLocalizedString("Conditions of use", comment: "alert button"), style: .default, handler: { _ in
UIApplication.shared.open(conditionsURL)
}),
howItWorks
]
: [okAlertAction, howItWorks]
}
}
}