ios: error handling

This commit is contained in:
Evgeny Poberezkin 2025-06-25 21:13:27 +01:00
parent b97a031d70
commit 1b9298fcee
No known key found for this signature in database
GPG key ID: 494BDDD9A28B577D
8 changed files with 155 additions and 139 deletions

View file

@ -899,8 +899,7 @@ func apiConnectPlan(connLink: String) async -> ((CreatedConnLink, ConnectionPlan
}
let r: APIResult<ChatResponse1> = 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>(_ r: APIResult<R>) -> 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<ChatResponse1> = 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<ChatResponse1> = 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?) {

View file

@ -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")

View file

@ -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 {

View file

@ -887,7 +887,6 @@ struct GroupPreferencesButton: View {
}
}
}
}

View file

@ -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 = "<group>"; };
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
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 = "<group>"; };
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a"; sourceTree = "<group>"; };
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 = "<group>"; };
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a"; sourceTree = "<group>"; };
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
@ -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 = "<group>";

View file

@ -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)

View file

@ -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

View file

@ -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()