diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 10fc470fdf..3317d22595 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -899,8 +899,7 @@ func apiConnectPlan(connLink: String) async -> ((CreatedConnLink, ConnectionPlan } let r: APIResult = await chatApiSendCmd(.apiConnectPlan(userId: userId, connLink: connLink)) if case let .result(.connectionPlan(_, connLink, connPlan)) = r { return ((connLink, connPlan), nil) } - let alert = apiConnectResponseAlert(r.unexpected) ?? connectionErrorAlert(r) - return (nil, alert) + return (nil, apiConnectResponseAlert(r)) } func apiConnect(incognito: Bool, connLink: CreatedConnLink) async -> (ConnReqType, PendingContactConnection)? { @@ -933,12 +932,11 @@ func apiConnect_(incognito: Bool, connLink: CreatedConnLink) async -> ((ConnReqT return (nil, alert) default: () } - let alert = apiConnectResponseAlert(r.unexpected) ?? connectionErrorAlert(r) - return (nil, alert) + return (nil, apiConnectResponseAlert(r)) } -private func apiConnectResponseAlert(_ r: ChatError) -> Alert? { - switch r { +private func apiConnectResponseAlert(_ r: APIResult) -> Alert { + switch r.unexpected { case .error(.invalidConnReq): mkAlert( title: "Invalid connection link", @@ -974,12 +972,12 @@ private func apiConnectResponseAlert(_ r: ChatError) -> Alert? { if internalErr == "SEUniqueID" { mkAlert( title: "Already connected?", - message: "It seems like you are already connected via this link. If it is not the case, there was an error (\(responseError(r)))." + message: "It seems like you are already connected via this link. If it is not the case, there was an error (\(internalErr))." ) } else { - nil + connectionErrorAlert(r) } - default: nil + default: connectionErrorAlert(r) } } @@ -1027,16 +1025,18 @@ func apiChangePreparedGroupUser(groupId: Int64, newUserId: Int64) async throws - throw r.unexpected } -func apiConnectPreparedContact(contactId: Int64, incognito: Bool, msg: MsgContent?) async throws -> Contact { - let r: ChatResponse1 = try await chatSendCmd(.apiConnectPreparedContact(contactId: contactId, incognito: incognito, msg: msg)) - if case let .startedConnectionToContact(_, contact) = r { return contact } - throw r.unexpected +func apiConnectPreparedContact(contactId: Int64, incognito: Bool, msg: MsgContent?) async -> Contact? { + let r: APIResult = await chatApiSendCmd(.apiConnectPreparedContact(contactId: contactId, incognito: incognito, msg: msg)) + if case let .result(.startedConnectionToContact(_, contact)) = r { return contact } + AlertManager.shared.showAlert(apiConnectResponseAlert(r)) + return nil } -func apiConnectPreparedGroup(groupId: Int64, incognito: Bool, msg: MsgContent?) async throws -> GroupInfo { - let r: ChatResponse1 = try await chatSendCmd(.apiConnectPreparedGroup(groupId: groupId, incognito: incognito, msg: msg)) - if case let .startedConnectionToGroup(_, groupInfo) = r { return groupInfo } - throw r.unexpected +func apiConnectPreparedGroup(groupId: Int64, incognito: Bool, msg: MsgContent?) async -> GroupInfo? { + let r: APIResult = await chatApiSendCmd(.apiConnectPreparedGroup(groupId: groupId, incognito: incognito, msg: msg)) + if case let .result(.startedConnectionToGroup(_, groupInfo)) = r { return groupInfo } + AlertManager.shared.showAlert(apiConnectResponseAlert(r)) + return nil } func apiConnectContactViaAddress(incognito: Bool, contactId: Int64) async -> (Contact?, Alert?) { diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index dfd1917196..0a34661a5c 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -405,14 +405,15 @@ struct ComposeView: View { } if chat.chatInfo.groupInfo?.nextConnectPrepared == true { - Button(action: connectPreparedGroup) { - if chat.chatInfo.groupInfo?.businessChat == nil { + if chat.chatInfo.groupInfo?.businessChat == nil { + Button(action: connectPreparedGroup) { Label("Join group", systemImage: "person.2.fill") - } else { - Label("Connect", systemImage: "briefcase.fill") } + .frame(height: 60) + .disabled(composeState.inProgress) + } else { + sendContactRequestView(disableSendButton, icon: "briefcase.fill", sendRequest: connectPreparedGroup) } - .frame(height: 60) } else if contact?.nextSendGrpInv == true { contextSendMessageToConnect("Send direct message to connect") Divider() @@ -428,24 +429,9 @@ struct ComposeView: View { Label("Connect", systemImage: "person.fill.badge.plus") } .frame(height: 60) + .disabled(composeState.inProgress) case .con: - HStack (alignment: .center) { - sendMessageView( - disableSendButton, - placeholder: NSLocalizedString("Add message", comment: "placeholder for sending contact request"), - sendToConnect: sendConnectPreparedContactRequest - ) - if composeState.whitespaceOnly { - Button(action: sendConnectPreparedContactRequest) { - HStack { - Text("Connect").fontWeight(.medium) - Image(systemName: "person.fill.badge.plus") - } - } - .padding(.horizontal, 8) - } - } - .padding(.horizontal, 12) + sendContactRequestView(disableSendButton, icon: "person.fill.badge.plus", sendRequest: sendConnectPreparedContactRequest) } } else if contact?.nextAcceptContactRequest == true, let crId = contact?.contactRequestId { ContextContactRequestActionsView(contactRequestId: crId) @@ -620,6 +606,27 @@ struct ComposeView: View { } } + private func sendContactRequestView(_ disableSendButton: Bool, icon: String, sendRequest: @escaping () -> Void) -> some View { + HStack (alignment: .center) { + sendMessageView( + disableSendButton, + placeholder: NSLocalizedString("Add message", comment: "placeholder for sending contact request"), + sendToConnect: sendRequest + ) + if composeState.whitespaceOnly { + Button(action: sendRequest) { + HStack { + Text("Connect").fontWeight(.medium) + Image(systemName: icon) + } + } + .padding(.horizontal, 8) + .disabled(composeState.inProgress) + } + } + .padding(.horizontal, 12) + } + private func sendMessageView(_ disableSendButton: Bool, placeholder: String? = nil, sendToConnect: (() -> Void)? = nil) -> some View { ZStack(alignment: .leading) { SendMessageView( @@ -695,6 +702,7 @@ struct ComposeView: View { Task { do { if let mc = connectCheckLinkPreview() { + await sending() let contact = try await apiSendMemberContactInvitation(chat.chatInfo.apiId, mc) await MainActor.run { self.chatModel.updateContact(contact) @@ -704,6 +712,7 @@ struct ComposeView: View { AlertManager.shared.showAlertMsg(title: "Empty message!") } } catch { + composeState.inProgress = false logger.error("ChatView.sendMemberContactInvitation error: \(error.localizedDescription)") AlertManager.shared.showAlertMsg(title: "Error sending member contact invitation", message: "Error: \(responseError(error))") } @@ -730,32 +739,30 @@ struct ComposeView: View { private func sendConnectPreparedContact() { Task { - do { - let mc = connectCheckLinkPreview() - let contact = try await apiConnectPreparedContact(contactId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc) + await sending() + let mc = connectCheckLinkPreview() + if let contact = await apiConnectPreparedContact(contactId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc) { await MainActor.run { self.chatModel.updateContact(contact) clearState() } - } catch { - logger.error("ChatView.sendConnectPreparedContact error: \(error.localizedDescription)") - AlertManager.shared.showAlertMsg(title: "Error connecting with contact", message: "Error: \(responseError(error))") + } else { + composeState.inProgress = false } } } private func connectPreparedGroup() { Task { - do { - let mc = connectCheckLinkPreview() - let groupInfo = try await apiConnectPreparedGroup(groupId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc) + await sending() + let mc = connectCheckLinkPreview() + if let groupInfo = await apiConnectPreparedGroup(groupId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc) { await MainActor.run { self.chatModel.updateGroup(groupInfo) clearState() } - } catch { - logger.error("ChatView.connectPreparedGroup error: \(error.localizedDescription)") - AlertManager.shared.showAlertMsg(title: "Error joining group", message: "Error: \(responseError(error))") + } else { + composeState.inProgress = false } } } @@ -1094,10 +1101,6 @@ struct ComposeView: View { } } - func sending() async { - await MainActor.run { composeState.inProgress = true } - } - func updateMessage(_ ei: ChatItem, live: Bool) async -> ChatItem? { if let oldMsgContent = ei.content.msgContent { do { @@ -1270,6 +1273,10 @@ struct ComposeView: View { } } + func sending() async { + await MainActor.run { composeState.inProgress = true } + } + private func startVoiceMessageRecording() async { startingRecording = true let fileName = generateNewFileName("voice", "m4a") diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift index ccb43f0ed6..dc9fbff9f6 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift @@ -13,60 +13,66 @@ struct ContextContactRequestActionsView: View { @EnvironmentObject var theme: AppTheme var contactRequestId: Int64 @UserDefault(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial + @State private var inProgress = false var body: some View { HStack(spacing: 0) { - Label("Reject", systemImage: "multiply") - .foregroundColor(.red) - .frame(maxWidth: .infinity) - .contentShape(Rectangle()) - .onTapGesture { - showRejectRequestAlert(contactRequestId) + Button(role: .destructive, action: showRejectRequestAlert) { + Label("Reject", systemImage: "multiply") } + .frame(maxWidth: .infinity, minHeight: 60) - Label("Accept", systemImage: "checkmark").foregroundColor(theme.colors.primary) - .frame(maxWidth: .infinity) - .contentShape(Rectangle()) - .onTapGesture { + Button { if ChatModel.shared.addressShortLinkDataSet { - Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) } + acceptRequest() } else { - showAcceptRequestAlert(contactRequestId) + showAcceptRequestAlert() } + } label: { + Label("Accept", systemImage: "checkmark") } + .frame(maxWidth: .infinity, minHeight: 60) } - .frame(minHeight: 60) + .disabled(inProgress) .frame(maxWidth: .infinity) .background(ToolbarMaterial.material(toolbarMaterial)) } -} -func showRejectRequestAlert(_ contactRequestId: Int64) { - showAlert( - NSLocalizedString("Reject contact request", comment: "alert title"), - message: NSLocalizedString("The sender will NOT be notified", comment: "alert message"), - actions: {[ - UIAlertAction(title: NSLocalizedString("Reject", comment: "alert action"), style: .destructive) { _ in - Task { await rejectContactRequest(contactRequestId, dismissToChatList: true) } - }, - cancelAlertAction - ]} - ) -} + private func showRejectRequestAlert() { + showAlert( + NSLocalizedString("Reject contact request", comment: "alert title"), + message: NSLocalizedString("The sender will NOT be notified", comment: "alert message"), + actions: {[ + UIAlertAction(title: NSLocalizedString("Reject", comment: "alert action"), style: .destructive) { _ in + Task { await rejectContactRequest(contactRequestId, dismissToChatList: true) } + }, + cancelAlertAction + ]} + ) + } -func showAcceptRequestAlert(_ contactRequestId: Int64) { - showAlert( - NSLocalizedString("Accept contact request", comment: "alert title"), - actions: {[ - UIAlertAction(title: NSLocalizedString("Accept", comment: "alert action"), style: .default) { _ in - Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) } - }, - UIAlertAction(title: NSLocalizedString("Accept incognito", comment: "alert action"), style: .default) { _ in - Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequestId) } - }, - cancelAlertAction - ]} - ) + private func showAcceptRequestAlert() { + showAlert( + NSLocalizedString("Accept contact request", comment: "alert title"), + actions: {[ + UIAlertAction(title: NSLocalizedString("Accept", comment: "alert action"), style: .default) { _ in + acceptRequest() + }, + UIAlertAction(title: NSLocalizedString("Accept incognito", comment: "alert action"), style: .default) { _ in + acceptRequest(incognito: true) + }, + cancelAlertAction + ]} + ) + } + + private func acceptRequest(incognito: Bool = false) { + inProgress = true + Task { + await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) + await MainActor.run { inProgress = false } + } + } } #Preview { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index a1c511adc0..9f5b687b8a 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -887,7 +887,6 @@ struct GroupPreferencesButton: View { } } } - } diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index d84d449b47..c4cdab85ca 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -178,8 +178,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -543,8 +543,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -704,8 +704,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -790,8 +790,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 3d36481e85..601497ed39 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -819,7 +819,7 @@ public enum SQLiteError: Decodable, Hashable { public enum AgentErrorType: Decodable, Hashable { case CMD(cmdErr: CommandErrorType, errContext: String) - case CONN(connErr: ConnectionErrorType) + case CONN(connErr: ConnectionErrorType, errContext: String) case SMP(serverAddress: String, smpErr: ProtocolErrorType) case NTF(ntfErr: ProtocolErrorType) case XFTP(xftpErr: XFTPErrorType) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index ca8dd81bd0..1d84091f8b 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1378,6 +1378,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { case .memGroupDeleted: return ("group is deleted", nil) case .memRemoved: return ("removed from group", nil) case .memLeft: return ("you left", nil) + case .memUnknown: return groupInfo.businessChat == nil ? ("can't send messages", nil) : nil default: return ("can't send messages", nil) } } @@ -1421,7 +1422,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { default: false } } - + public var groupInfo: GroupInfo? { switch self { case let .group(groupInfo, _): return groupInfo @@ -1531,7 +1532,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { public func ntfsEnabled(chatItem: ChatItem) -> Bool { ntfsEnabled(chatItem.meta.userMention) } - + public func ntfsEnabled(_ userMention: Bool) -> Bool { switch self.chatSettings?.enableNtfs { case .all: true @@ -1547,7 +1548,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { default: return nil } } - + public var nextNtfMode: MsgFilter? { self.chatSettings?.enableNtfs.nextMode(mentions: hasMentions) } @@ -1596,7 +1597,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { case .invalidJSON: return .now } } - + public func ttl(_ globalTTL: ChatItemTTL) -> ChatTTL { switch self { case let .direct(contact): @@ -1644,7 +1645,7 @@ public struct ChatData: Decodable, Identifiable, Hashable, ChatLike { self.chatItems = chatItems self.chatStats = chatStats } - + public static func invalidJSON(_ json: Data?) -> ChatData { ChatData( chatInfo: .invalidJSON(json: json), @@ -2085,15 +2086,14 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { var createdAt: Date var updatedAt: Date var chatTs: Date? - public var connLinkToConnect: CreatedConnLink? - public var connLinkStartedConnection: Bool + public var preparedGroup: PreparedGroup? public var uiThemes: ThemeModeOverrides? public var membersRequireAttention: Int public var id: ChatId { get { "#\(groupId)" } } public var apiId: Int64 { get { groupId } } public var ready: Bool { get { true } } - public var nextConnectPrepared: Bool { connLinkToConnect != nil && !connLinkStartedConnection } + public var nextConnectPrepared: Bool { if let preparedGroup { !preparedGroup.connLinkStartedConnection } else { false } } public var displayName: String { localAlias == "" ? groupProfile.displayName : localAlias } public var fullName: String { get { groupProfile.fullName } } public var image: String? { get { groupProfile.image } } @@ -2134,13 +2134,17 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { chatSettings: ChatSettings.defaults, createdAt: .now, updatedAt: .now, - connLinkStartedConnection: false, membersRequireAttention: 0, chatTags: [], localAlias: "" ) } +public struct PreparedGroup: Decodable, Hashable { + public var connLinkToConnect: CreatedConnLink? + public var connLinkStartedConnection: Bool +} + public struct GroupRef: Decodable, Hashable { public var groupId: Int64 var localDisplayName: GroupName @@ -2298,7 +2302,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable { ? String.localizedStringWithFormat(NSLocalizedString("Past member %@", comment: "past/unknown group member"), name) : name } - + public var localAliasAndFullName: String { get { let p = memberProfile @@ -2378,7 +2382,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable { return memberStatus != .memRemoved && memberStatus != .memLeft && memberRole < .admin && userRole >= .admin && userRole >= memberRole && groupInfo.membership.memberActive } - + public var canReceiveReports: Bool { memberRole >= .moderator && versionRange.maxVersion >= REPORTS_VERSION } @@ -2390,7 +2394,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable { memberChatVRange } } - + public var memberIncognito: Bool { memberProfile.profileId != memberContactProfileId } @@ -2446,7 +2450,7 @@ public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Cod public var id: Self { self } public static var supportedRoles: [GroupMemberRole] = [.observer, .member, .admin, .owner] - + public var text: String { switch self { case .observer: return NSLocalizedString("observer", comment: "member role") @@ -2602,7 +2606,7 @@ public enum ConnectionEntity: Decodable, Hashable { nil } } - + // public var localDisplayName: String? { // switch self { // case let .rcvDirectMsgConnection(conn, contact): @@ -2643,7 +2647,7 @@ public struct NtfMsgInfo: Decodable, Hashable { public enum RcvNtfMsgInfo: Decodable { case info(ntfMsgInfo: NtfMsgInfo?) case error(ntfMsgError: AgentErrorType) - + @inline(__always) public var noMsg: Bool { if case let .info(msg) = self { msg == nil } else { true } @@ -2703,7 +2707,7 @@ public struct CIMentionMember: Decodable, Hashable { public struct CIMention: Decodable, Hashable { public var memberId: String public var memberRef: CIMentionMember? - + public init(groupMember m: GroupMember) { self.memberId = m.memberId self.memberRef = CIMentionMember( @@ -2954,7 +2958,7 @@ public struct ChatItem: Identifiable, Decodable, Hashable { default: return true } } - + public var isReport: Bool { switch content { case let .sndMsgContent(msgContent), let .rcvMsgContent(msgContent): @@ -3054,14 +3058,14 @@ public struct ChatItem: Identifiable, Decodable, Hashable { file: nil ) } - + public static func getReportSample(text: String, reason: ReportReason, item: ChatItem, sender: GroupMember? = nil) -> ChatItem { let chatDir = if let sender = sender { CIDirection.groupRcv(groupMember: sender) } else { CIDirection.groupSnd } - + return ChatItem( chatDir: chatDir, meta: CIMeta( @@ -3175,7 +3179,7 @@ public enum CIDirection: Decodable, Hashable { } } } - + public func sameDirection(_ dir: CIDirection) -> Bool { switch (self, dir) { case let (.groupRcv(m1), .groupRcv(m2)): m1.groupMemberId == m2.groupMemberId @@ -3311,7 +3315,7 @@ public enum CIStatus: Decodable, Hashable { case .invalid: return "invalid" } } - + public var sent: Bool { switch self { case .sndNew: true @@ -4124,7 +4128,7 @@ public enum FileError: Decodable, Equatable, Hashable { case let .other(fileError): String.localizedStringWithFormat(NSLocalizedString("Error: %@", comment: "file error text"), fileError) } } - + public var moreInfoButton: (label: LocalizedStringKey, link: URL)? { switch self { case .blocked: ("How it works", contentModerationPostLink) @@ -4426,7 +4430,7 @@ public enum ReportReason: Hashable { case profile case other case unknown(type: String) - + public static var supportedReasons: [ReportReason] = [.spam, .illegal, .community, .profile, .other] public var text: String { @@ -4439,7 +4443,7 @@ public enum ReportReason: Hashable { case let .unknown(type): return type } } - + public var attrString: NSAttributedString { let descr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) return NSAttributedString(string: text.isEmpty ? self.text : "\(self.text): ", attributes: [ @@ -4486,7 +4490,7 @@ public struct LinkPreview: Codable, Equatable, Hashable { self.description = description self.image = image } - + public var uri: URL public var title: String // TODO remove once optional in haskell @@ -4535,7 +4539,7 @@ public enum NtfTknStatus: String, Decodable, Hashable { case .expired: NSLocalizedString("Expired", comment: "token status text") } } - + public func info(register: Bool) -> String { switch self { case .new: return NSLocalizedString("Please wait for token to be registered.", comment: "token info") @@ -4913,9 +4917,9 @@ public enum ChatItemTTL: Identifiable, Comparable, Hashable { public enum ChatTTL: Identifiable, Hashable { case userDefault(ChatItemTTL) case chat(ChatItemTTL) - + public var id: Self { self } - + public var text: String { switch self { case let .chat(ttl): return ttl.deleteAfterText @@ -4924,21 +4928,21 @@ public enum ChatTTL: Identifiable, Hashable { ttl.deleteAfterText) } } - + public var neverExpires: Bool { switch self { case let .chat(ttl): return ttl.seconds == 0 case let .userDefault(ttl): return ttl.seconds == 0 } } - + public var value: Int64? { switch self { case let .chat(ttl): return ttl.seconds case .userDefault: return nil } } - + public var usingDefault: Bool { switch self { case .userDefault: return true @@ -4951,9 +4955,9 @@ public struct ChatTag: Decodable, Hashable { public var chatTagId: Int64 public var chatTagText: String public var chatTagEmoji: String? - + public var id: Int64 { chatTagId } - + public init(chatTagId: Int64, chatTagText: String, chatTagEmoji: String?) { self.chatTagId = chatTagId self.chatTagText = chatTagText diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 538393bb62..e511f30ff7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -7055,7 +7055,7 @@ sealed class SQLiteError { sealed class AgentErrorType { val string: String get() = when (this) { is CMD -> "CMD ${cmdErr.string} $errContext" - is CONN -> "CONN ${connErr.string}" + is CONN -> "CONN ${connErr.string} $errContext" is SMP -> "SMP ${smpErr.string}" // is NTF -> "NTF ${ntfErr.string}" is XFTP -> "XFTP ${xftpErr.string}" @@ -7068,7 +7068,7 @@ sealed class AgentErrorType { is INACTIVE -> "INACTIVE" } @Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType, val errContext: String): AgentErrorType() - @Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType() + @Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType, val errContext: String): AgentErrorType() @Serializable @SerialName("SMP") class SMP(val serverAddress: String, val smpErr: SMPErrorType): AgentErrorType() // @Serializable @SerialName("NTF") class NTF(val ntfErr: SMPErrorType): AgentErrorType() @Serializable @SerialName("XFTP") class XFTP(val xftpErr: XFTPErrorType): AgentErrorType()