This commit is contained in:
spaced4ndy
2025-10-13 12:43:50 +04:00
parent 92712ff2e1
commit 72b20f9fb2
6 changed files with 101 additions and 72 deletions
+3 -35
View File
@@ -1053,7 +1053,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest, chat_: ChatData?)
case contactUpdated(user: UserRef, toContact: Contact)
case groupMemberUpdated(user: UserRef, groupInfo: GroupInfo, fromMember: GroupMember, toMember: GroupMember)
case networkStatus(networkStatus: NetworkStatus, connections: [String])
case subscriptionStatus(subscriptionStatus: SubscriptionStatus, connections: [String])
case chatInfoUpdated(user: UserRef, chatInfo: ChatInfo)
case newChatItems(user: UserRef, chatItems: [AChatItem])
case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem])
@@ -1130,7 +1130,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
case .receivedContactRequest: "receivedContactRequest"
case .contactUpdated: "contactUpdated"
case .groupMemberUpdated: "groupMemberUpdated"
case .networkStatus: "networkStatus"
case .subscriptionStatus: "subscriptionStatus"
case .chatInfoUpdated: "chatInfoUpdated"
case .newChatItems: "newChatItems"
case .chatItemsStatusesUpdated: "chatItemsStatusesUpdated"
@@ -1202,7 +1202,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
case let .receivedContactRequest(u, contactRequest, chat_): return withUser(u, "contactRequest: \(String(describing: contactRequest))\nchat_: \(String(describing: chat_))")
case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact))
case let .groupMemberUpdated(u, groupInfo, fromMember, toMember): return withUser(u, "groupInfo: \(groupInfo)\nfromMember: \(fromMember)\ntoMember: \(toMember)")
case let .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))"
case let .subscriptionStatus(status, conns): return "subscriptionStatus: \(String(describing: status))\nconnections: \(String(describing: conns))"
case let .chatInfoUpdated(u, chatInfo): return withUser(u, String(describing: chatInfo))
case let .newChatItems(u, chatItems):
let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n")
@@ -1362,38 +1362,6 @@ enum ChatDeleteMode: Codable {
}
}
enum NetworkStatus: Decodable, Equatable {
case unknown
case connected
case disconnected
case error(connectionError: String)
var statusString: LocalizedStringKey {
switch self {
case .connected: "connected"
case .error: "error"
default: "connecting"
}
}
var statusExplanation: LocalizedStringKey {
switch self {
case .connected: "You are connected to the server used to receive messages from this contact."
case let .error(err): "Trying to connect to the server used to receive messages from this contact (error: \(err))."
default: "Trying to connect to the server used to receive messages from this contact."
}
}
var imageName: String {
switch self {
case .unknown: "circle.dotted"
case .connected: "circle.fill"
case .disconnected: "ellipsis.circle.fill"
case .error: "exclamationmark.circle.fill"
}
}
}
enum ForwardConfirmation: Decodable, Hashable {
case filesNotAccepted(fileIds: [Int64])
case filesInProgress(filesCount: Int)
+2
View File
@@ -351,6 +351,8 @@ final class ChatModel: ObservableObject {
@Published var deletedChats: Set<String> = []
// current chat
@Published var chatId: String?
@Published var chatAgentConnId: String?
@Published var chatSubStatus: SubscriptionStatus?
@Published var openAroundItemId: ChatItem.ID? = nil
@Published var chatToTop: String?
@Published var groupMembers: [GMember] = []
+6 -3
View File
@@ -2322,9 +2322,12 @@ func processReceivedMsg(_ res: ChatEvent) async {
_ = m.upsertGroupMember(groupInfo, toMember)
}
}
case let .networkStatus(status, connections):
// TODO [sub status] update status for current chat in model if id matches
return
case let .subscriptionStatus(status, connections):
if let chatAgentConnId = m.chatAgentConnId, connections.contains(chatAgentConnId) {
await MainActor.run {
m.chatSubStatus = status
}
}
case let .chatInfoUpdated(user, chatInfo):
if active(user) {
await MainActor.run {
+16 -19
View File
@@ -114,7 +114,7 @@ struct ChatInfoView: View {
enum ChatInfoViewAlert: Identifiable {
case clearChatAlert
case networkStatusAlert
case subStatusAlert(status: SubscriptionStatus)
case switchAddressAlert
case abortSwitchAddressAlert
case syncConnectionForceAlert
@@ -125,7 +125,7 @@ struct ChatInfoView: View {
var id: String {
switch self {
case .clearChatAlert: return "clearChatAlert"
case .networkStatusAlert: return "networkStatusAlert"
case let .subStatusAlert(status): return "subStatusAlert \(status)"
case .switchAddressAlert: return "switchAddressAlert"
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
@@ -235,10 +235,12 @@ struct ChatInfoView: View {
if contact.ready && contact.active {
Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) {
networkStatusRow()
.onTapGesture {
alert = .networkStatusAlert
}
if let chatSubStatus = chatModel.chatSubStatus {
subStatusRow(chatSubStatus)
.onTapGesture {
alert = .subStatusAlert(status: chatSubStatus)
}
}
if let connStats = connectionStats {
Button("Change receiving address") {
alert = .switchAddressAlert
@@ -324,7 +326,7 @@ struct ChatInfoView: View {
.alert(item: $alert) { alertItem in
switch(alertItem) {
case .clearChatAlert: return clearChatAlert()
case .networkStatusAlert: return networkStatusAlert()
case let .subStatusAlert(status): return subStatusAlert(status)
case .switchAddressAlert: return switchAddressAlert(switchContactAddress)
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress)
case .syncConnectionForceAlert:
@@ -545,26 +547,22 @@ struct ChatInfoView: View {
}
}
private func networkStatusRow() -> some View {
private func subStatusRow(_ status: SubscriptionStatus) -> some View {
HStack {
Text("Network status")
Image(systemName: "info.circle")
.foregroundColor(theme.colors.primary)
.font(.system(size: 14))
Spacer()
// TODO [sub status] use status from model
// Text(networkModel.contactNetworkStatus(contact).statusString)
Text(NetworkStatus.connected.statusString)
Text(status.statusString)
.foregroundColor(theme.colors.secondary)
serverImage()
serverImage(status)
}
}
private func serverImage() -> some View {
// TODO [sub status] use status from model
let status = NetworkStatus.connected // networkModel.contactNetworkStatus(contact)
private func serverImage(_ status: SubscriptionStatus) -> some View {
return Image(systemName: status.imageName)
.foregroundColor(status == .connected ? .green : theme.colors.secondary)
.foregroundColor(status == .active ? .green : theme.colors.secondary)
.font(.system(size: 12))
}
@@ -607,11 +605,10 @@ struct ChatInfoView: View {
)
}
private func networkStatusAlert() -> Alert {
private func subStatusAlert(_ status: SubscriptionStatus) -> Alert {
Alert(
title: Text("Network status"),
// TODO [sub status] use status from model
message: Text("") // Text(networkModel.contactNetworkStatus(contact).statusExplanation)
message: Text(status.statusExplanation)
)
}
+24 -10
View File
@@ -393,6 +393,8 @@ struct ChatView: View {
if chatModel.chatId == cInfo.id && !presentationMode.wrappedValue.isPresented {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
if chatModel.chatId == nil {
chatModel.chatAgentConnId = nil
chatModel.chatSubStatus = nil
im.reversedChatItems = []
im.chatState.clear()
chatModel.groupMembers = []
@@ -657,18 +659,30 @@ struct ChatView: View {
private func initChatView() {
let cInfo = chat.chatInfo
// This check prevents the call to apiContactInfo after the app is suspended, and the database is closed.
if case .active = scenePhase,
case let .direct(contact) = cInfo {
Task {
do {
let (stats, _) = try await apiContactInfo(chat.chatInfo.apiId)
await MainActor.run {
if let s = stats {
chatModel.updateContactConnectionStats(contact, s)
if case .active = scenePhase {
if case let .direct(contact) = cInfo {
Task {
do {
let (stats, _) = try await apiContactInfo(chat.chatInfo.apiId)
await MainActor.run {
if let s = stats {
chatModel.updateContactConnectionStats(contact, s)
if let contactConn = contact.activeConn {
chatModel.chatAgentConnId = contactConn.agentConnId
chatModel.chatSubStatus = s.subStatus
}
}
}
} catch let error {
logger.error("apiContactInfo error: \(responseError(error))")
}
}
} else {
Task {
await MainActor.run {
chatModel.chatAgentConnId = nil
chatModel.chatSubStatus = nil
}
} catch let error {
logger.error("apiContactInfo error: \(responseError(error))")
}
}
}
+50 -5
View File
@@ -539,13 +539,13 @@ public enum MsgFilter: String, Codable, Hashable {
}
}
// TODO [sub status] add SubscriptionStatus, QueueStatus
public struct ConnectionStats: Decodable, Hashable {
public var connAgentVersion: Int
public var rcvQueuesInfo: [RcvQueueInfo]
public var sndQueuesInfo: [SndQueueInfo]
public var ratchetSyncState: RatchetSyncState
public var ratchetSyncSupported: Bool
public var subStatus: SubscriptionStatus?
public var ratchetSyncAllowed: Bool {
ratchetSyncSupported && [.allowed, .required].contains(ratchetSyncState)
@@ -560,25 +560,28 @@ public struct ConnectionStats: Decodable, Hashable {
}
}
public struct RcvQueueInfo: Codable, Hashable {
public struct RcvQueueInfo: Decodable, Hashable {
public var rcvServer: String
public var status: QueueStatus
public var rcvSwitchStatus: RcvSwitchStatus?
public var canAbortSwitch: Bool
public var subStatus: SubscriptionStatus
}
public enum RcvSwitchStatus: String, Codable, Hashable {
public enum RcvSwitchStatus: String, Decodable, Hashable {
case switchStarted = "switch_started"
case sendingQADD = "sending_qadd"
case sendingQUSE = "sending_quse"
case receivedMessage = "received_message"
}
public struct SndQueueInfo: Codable, Hashable {
public struct SndQueueInfo: Decodable, Hashable {
public var sndServer: String
public var status: QueueStatus
public var sndSwitchStatus: SndSwitchStatus?
}
public enum SndSwitchStatus: String, Codable, Hashable {
public enum SndSwitchStatus: String, Decodable, Hashable {
case sendingQKEY = "sending_qkey"
case sendingQTEST = "sending_qtest"
}
@@ -607,6 +610,48 @@ public enum RatchetSyncState: String, Decodable {
case agreed
}
public enum QueueStatus: String, Decodable, Hashable {
case new
case confirmed
case secured
case active
case disabled
}
public enum SubscriptionStatus: Decodable, Hashable {
case active
case pending
case removed(subError: String)
case noSub
public var statusString: LocalizedStringKey {
switch self {
case .active: "connected"
case .pending: "connecting"
case .removed: "error"
case .noSub: "no subscription"
}
}
public var statusExplanation: LocalizedStringKey {
switch self {
case .active: "You are connected to the server used to receive messages from this contact."
case .pending: "Trying to connect to the server used to receive messages from this contact."
case let .removed(err): "Error connecting to the server used to receive messages from this contact: \(err)."
case .noSub: "You are not connected to the server used to receive messages from this contact (no subscription)."
}
}
public var imageName: String {
switch self {
case .active: "circle.fill"
case .pending: "ellipsis.circle.fill"
case .removed: "exclamationmark.circle.fill"
case .noSub: "circle.dotted"
}
}
}
public protocol SelectableItem: Identifiable, Equatable {
var label: LocalizedStringKey { get }
static var values: [Self] { get }