From 35f5687f4f86d0d54550182ca1cd9bb7b28c48e8 Mon Sep 17 00:00:00 2001 From: Chris Offner <chrisoffner@pm.me> Date: Tue, 23 Mar 2021 11:24:47 +0100 Subject: [PATCH] Code cleanup & style, commented out redundant, incomplete code that caused warnings. --- enzevalos_iphone.xcodeproj/project.pbxproj | 1 - enzevalos_iphone/MailSession.swift | 450 +++++++++++------- enzevalos_iphone/PGP/SwiftPGP.swift | 97 ++-- .../SwiftUI/Compose/ComposeModel.swift | 7 +- .../SwiftUI/Compose/ComposeView.swift | 225 +++++---- .../SwiftUI/Compose/RecipientFieldModel.swift | 43 +- .../SwiftUI/Compose/RecipientListView.swift | 11 +- .../SwiftUI/Compose/RecipientRowView.swift | 2 +- .../SwiftUI/Compose/RecipientsModel.swift | 1 + .../SwiftUI/Inbox/InboxView.swift | 41 +- .../SwiftUI/Inbox/MailListView.swift | 60 ++- .../persistentData/AddressRecord.swift | 5 +- .../persistentData/MailRecord.swift | 15 +- 13 files changed, 605 insertions(+), 353 deletions(-) diff --git a/enzevalos_iphone.xcodeproj/project.pbxproj b/enzevalos_iphone.xcodeproj/project.pbxproj index 262cbaeb..c2030646 100644 --- a/enzevalos_iphone.xcodeproj/project.pbxproj +++ b/enzevalos_iphone.xcodeproj/project.pbxproj @@ -1945,7 +1945,6 @@ 3EC35F2420037651008BDF95 /* InvitationHelper.swift in Sources */, 47C112C22531D72E00621A07 /* PublicKeyRecord.swift in Sources */, 3FB75DCD25FFD37400919925 /* RecipientListView.swift in Sources */, - 472F39901E252470009260FB /* CNMailAddressesExtension.swift in Sources */, 477120CD254C76AE00B28C64 /* FolderOverView.swift in Sources */, 4733B202252B142C00AB5600 /* Properties.swift in Sources */, 47FAE3492524FB58005A1BCB /* AddressRecord.swift in Sources */, diff --git a/enzevalos_iphone/MailSession.swift b/enzevalos_iphone/MailSession.swift index c9ccdd74..d4d6cf2c 100644 --- a/enzevalos_iphone/MailSession.swift +++ b/enzevalos_iphone/MailSession.swift @@ -32,7 +32,11 @@ class MailServer: Comparable { } static func == (lhs: MailServer, rhs: MailServer) -> Bool { - return lhs.sessionType == rhs.sessionType && lhs.hostname == rhs.hostname && lhs.port == rhs.port && lhs.connectionType == rhs.connectionType && lhs.authType == rhs.authType + return lhs.sessionType == rhs.sessionType + && lhs.hostname == rhs.hostname + && lhs.port == rhs.port + && lhs.connectionType == rhs.connectionType + && lhs.authType == rhs.authType } private static let maxWaitingSeconds = 15 @@ -60,16 +64,18 @@ class MailServer: Comparable { var possibleAuthTypes: [MCOAuthType] = [] var toTestConnType: Set<MCOConnectionType> = [] - var toTestServers: [(hostname: String, port: UInt32, authTyp: MCOAuthType, connType: MCOConnectionType)] = [] + var toTestServers: [(hostname: String, + port: UInt32, + authTyp: MCOAuthType, + connType: MCOConnectionType)] = [] var createdTestServers = false - var receivedAuthTypes = false var loggerCalled = false var works = false var sendCallback = false var isTLS: Bool { - get{ + get { return connectionType == MCOConnectionType.TLS } } @@ -90,9 +96,16 @@ class MailServer: Comparable { return msg } } - - init(sessionType: SessionType, username: String, password: String, hostname: String, port: UInt32, connectionType: MCOConnectionType?, authType: MCOAuthType? = nil, callback: @escaping (_ error: MailServerConnectionError?, _ server: MailServer) -> ()) { + + init(sessionType: SessionType, + username: String, + password: String, + hostname: String, + port: UInt32, + connectionType: MCOConnectionType?, + authType: MCOAuthType? = nil, + callback: @escaping (_ error: MailServerConnectionError?, _ server: MailServer) -> ()) { self.sessionType = sessionType self.hostname = hostname self.port = port @@ -101,78 +114,115 @@ class MailServer: Comparable { if let connectionType = connectionType { self.connectionType = connectionType self.toTestConnType = [] - } - else if port == 143 || port == 587 || port == 25 { + } else if port == 143 || port == 587 || port == 25 { self.connectionType = .startTLS - - } - else { - self.connectionType = .TLS + } else { + self.connectionType = .TLS } self.username = username self.password = password - self.callback = callback } - convenience init(sessionType: SessionType, username: String, password: String, serverConfig: MCONetService, callback: @escaping (_ error: MailServerConnectionError?, _ server: MailServer) -> ()) throws { - if let serverInfo = serverConfig.info(), let hostname = serverInfo["hostname"] as? String, let port = serverInfo["port"] as? UInt32 { + convenience init(sessionType: SessionType, + username: String, + password: String, + serverConfig: MCONetService, + callback: @escaping (_ error: MailServerConnectionError?, + _ server: MailServer) -> ()) throws { + if let serverInfo = serverConfig.info(), + let hostname = serverInfo["hostname"] as? String, + let port = serverInfo["port"] as? UInt32 { var conn: MCOConnectionType? = nil + if let trans = serverInfo["ssl"] as? Bool, trans { conn = MCOConnectionType.TLS } + if let trans = serverInfo["starttls"] as? Bool, trans { conn = MCOConnectionType.startTLS } - self.init(sessionType: sessionType, username: username, password: password, hostname: hostname, port: port, connectionType: conn, callback: callback) - } - else { - throw MailServerConnectionError.NoData + + self.init(sessionType: sessionType, + username: username, + password: password, + hostname: hostname, + port: port, + connectionType: conn, + callback: callback) + } else { + throw MailServerConnectionError.NoData } } - convenience init(userValues sessionType: SessionType, callback: @escaping (_ error: MailServerConnectionError?, _ server: MailServer) -> ()) throws{ + convenience init(userValues sessionType: SessionType, + callback: @escaping (_ error: MailServerConnectionError?, + _ server: MailServer) -> ()) throws { if let username = UserManager.loadUserValue(Attribute.accountname) as? String, - let password = UserManager.loadUserValue(Attribute.userPW) as? String { - if sessionType == SessionType.IMAP, let hostname = UserManager.loadUserValue(Attribute.imapHostname) as? String, let port = UserManager.loadUserValue(Attribute.imapPort) as? UInt32{ - var connType: MCOConnectionType? = nil - if let rawtype = UserManager.loadUserValue(Attribute.imapConnectionType) as? Int { - connType = MCOConnectionType.init(rawValue: rawtype) - } - var authType: MCOAuthType? = nil - if let rawtype = UserManager.loadUserValue(Attribute.smtpAuthType) as? Int { - authType = MCOAuthType.init(rawValue: rawtype) - } - self.init(sessionType: sessionType, username: username, password: password, hostname: hostname , port: port, connectionType: connType, authType: authType, callback: callback) - if let authType = UserManager.loadUserValue(Attribute.imapAuthType) as? Int { - self.authType = MCOAuthType.init(rawValue: authType) - } - + let password = UserManager.loadUserValue(Attribute.userPW) as? String { + if sessionType == SessionType.IMAP, + let hostname = UserManager.loadUserValue(Attribute.imapHostname) as? String, + let port = UserManager.loadUserValue(Attribute.imapPort) as? UInt32 { + var connType: MCOConnectionType? = nil + if let rawtype = UserManager.loadUserValue(Attribute.imapConnectionType) as? Int { + connType = MCOConnectionType.init(rawValue: rawtype) } - else if let hostname = UserManager.loadUserValue(Attribute.smtpHostname) as? String, let port = UserManager.loadUserValue(Attribute.smtpPort) as? UInt32 { - var connType: MCOConnectionType? = nil - if let rawtype = UserManager.loadUserValue(Attribute.smtpConnectionType) as? Int { - connType = MCOConnectionType.init(rawValue: rawtype) - } - var authType: MCOAuthType? = nil - if let rawtype = UserManager.loadUserValue(Attribute.smtpAuthType) as? Int { - authType = MCOAuthType.init(rawValue: rawtype) - } - self.init(sessionType: sessionType, username: username, password: password, hostname: hostname, port: port, connectionType: connType, authType: authType, callback: callback) - if let authType = UserManager.loadUserValue(Attribute.smtpAuthType) as? Int { - self.authType = MCOAuthType.init(rawValue: authType) - } + + var authType: MCOAuthType? = nil + if let rawtype = UserManager.loadUserValue(Attribute.smtpAuthType) as? Int { + authType = MCOAuthType.init(rawValue: rawtype) } - else { - throw MailServerConnectionError.NoData + + self.init(sessionType: sessionType, + username: username, + password: password, + hostname: hostname, + port: port, + connectionType: connType, + authType: authType, + callback: callback) + if let authType = UserManager.loadUserValue(Attribute.imapAuthType) as? Int { + self.authType = MCOAuthType.init(rawValue: authType) } - } - else { + } else if let hostname = UserManager.loadUserValue(Attribute.smtpHostname) as? String, + let port = UserManager.loadUserValue(Attribute.smtpPort) as? UInt32 { + var connType: MCOConnectionType? = nil + + if let rawtype = UserManager.loadUserValue(Attribute.smtpConnectionType) as? Int { + connType = MCOConnectionType.init(rawValue: rawtype) + } + + var authType: MCOAuthType? = nil + if let rawtype = UserManager.loadUserValue(Attribute.smtpAuthType) as? Int { + authType = MCOAuthType.init(rawValue: rawtype) + } + + self.init(sessionType: sessionType, + username: username, + password: password, + hostname: hostname, + port: port, + connectionType: connType, + authType: authType, + callback: callback) + + if let authType = UserManager.loadUserValue(Attribute.smtpAuthType) as? Int { + self.authType = MCOAuthType.init(rawValue: authType) + } + } else { + throw MailServerConnectionError.NoData + } + } else { throw MailServerConnectionError.NoData } } - convenience init(defaultValues sessionType: SessionType, mailAddr: String, username: String, password: String, callback: @escaping (_ error: MailServerConnectionError?, _ server: MailServer) -> ()) { + convenience init(defaultValues sessionType: SessionType, + mailAddr: String, + username: String, + password: String, + callback: @escaping (_ error: MailServerConnectionError?, + _ server: MailServer) -> ()) { var auth = MCOAuthType.init(rawValue: 0) var conn = MCOConnectionType.TLS var port: UInt32 = 111 @@ -186,43 +236,51 @@ class MailServer: Comparable { if let authTypeValue = Attribute.imapAuthType.defaultValue as? Int { auth = MCOAuthType.init(rawValue: authTypeValue) } + if let connectionTypeValue = Attribute.imapConnectionType.defaultValue as? Int { conn = MCOConnectionType.init(rawValue: connectionTypeValue) } + if let name = Attribute.imapHostname.defaultValue as? String, domain == nil { hostname = name + } else if !MailSession.IMAPPREFIX.isEmpty{ + hostname = MailSession.IMAPPREFIX[0] + "." + hostname } - else if !MailSession.IMAPPREFIX.isEmpty{ - hostname = MailSession.IMAPPREFIX[0]+"."+hostname - } + if let p = Attribute.imapPort.defaultValue as? UInt32 { port = p - } - else if !MailSession.IMAPPORT.isEmpty { + } else if !MailSession.IMAPPORT.isEmpty { port = MailSession.IMAPPORT[0] } - } - else { + } else { if let authTypeValue = Attribute.smtpAuthType.defaultValue as? Int { auth = MCOAuthType.init(rawValue: authTypeValue) } + if let connectionTypeValue = Attribute.smtpConnectionType.defaultValue as? Int { conn = MCOConnectionType.init(rawValue: connectionTypeValue) } + if let name = Attribute.smtpHostname.defaultValue as? String, domain == nil { hostname = name - } - else if !MailSession.SMTPPREFIX.isEmpty{ + } else if !MailSession.SMTPPREFIX.isEmpty{ hostname = MailSession.SMTPPREFIX[0]+"."+hostname } + if let p = Attribute.smtpPort.defaultValue as? UInt32 { port = p - } - else if !MailSession.SMTPPORT.isEmpty { + } else if !MailSession.SMTPPORT.isEmpty { port = MailSession.SMTPPORT[0] } } - self.init(sessionType: sessionType, username: username, password: password, hostname: hostname, port: port, connectionType: conn, authType: auth, callback: callback) + self.init(sessionType: sessionType, + username: username, + password: password, + hostname: hostname, + port: port, + connectionType: conn, + authType: auth, + callback: callback) } func storeToUserDefaults(mailAddr: String) -> Bool { @@ -230,91 +288,124 @@ class MailServer: Comparable { let connectionTypeValue = connectionType.rawValue let authTypeValue = authType.rawValue if sessionType == SessionType.IMAP { - UserManager.storeUserValue(hostname as AnyObject, attribute: Attribute.imapHostname) - UserManager.storeUserValue(port as AnyObject, attribute: Attribute.imapPort) - UserManager.storeUserValue(connectionTypeValue as AnyObject, attribute: Attribute.imapConnectionType) - UserManager.storeUserValue(authTypeValue as AnyObject, attribute: Attribute.imapAuthType) - } - else { - UserManager.storeUserValue(hostname as AnyObject, attribute: Attribute.smtpHostname) - UserManager.storeUserValue(port as AnyObject, attribute: Attribute.smtpPort) - UserManager.storeUserValue(connectionTypeValue as AnyObject, attribute: Attribute.smtpConnectionType) - UserManager.storeUserValue(authTypeValue as AnyObject, attribute: Attribute.smtpAuthType) - } - UserManager.storeUserValue(password as AnyObject, attribute: Attribute.userPW) - UserManager.storeUserValue(username as AnyObject, attribute: Attribute.accountname) - UserManager.storeUserValue(mailAddr.lowercased() as AnyObject, attribute: Attribute.userAddr) - UserManager.storeUserValue(mailAddr.lowercased() as AnyObject, attribute: Attribute.userDisplayName) + UserManager.storeUserValue(hostname as AnyObject, + attribute: Attribute.imapHostname) + UserManager.storeUserValue(port as AnyObject, + attribute: Attribute.imapPort) + UserManager.storeUserValue(connectionTypeValue as AnyObject, + attribute: Attribute.imapConnectionType) + UserManager.storeUserValue(authTypeValue as AnyObject, + attribute: Attribute.imapAuthType) + } else { + UserManager.storeUserValue(hostname as AnyObject, + attribute: Attribute.smtpHostname) + UserManager.storeUserValue(port as AnyObject, + attribute: Attribute.smtpPort) + UserManager.storeUserValue(connectionTypeValue as AnyObject, + attribute: Attribute.smtpConnectionType) + UserManager.storeUserValue(authTypeValue as AnyObject, + attribute: Attribute.smtpAuthType) + } + UserManager.storeUserValue(password as AnyObject, + attribute: Attribute.userPW) + UserManager.storeUserValue(username as AnyObject, + attribute: Attribute.accountname) + UserManager.storeUserValue(mailAddr.lowercased() as AnyObject, + attribute: Attribute.userAddr) + UserManager.storeUserValue(mailAddr.lowercased() as AnyObject, + attribute: Attribute.userDisplayName) return true } - return false + return false } - func createSMTPSession(logging: Bool = true, credentials: Bool = false, withAuthType: Bool = true) -> MCOSMTPSession? { + func createSMTPSession(logging: Bool = true, + credentials: Bool = false, + withAuthType: Bool = true) -> MCOSMTPSession? { loggerCalled = false guard sessionType == .SMTP else { return nil } let session = MCOSMTPSession() session.timeout = TimeInterval(MailServer.maxWaitingSeconds) - session.hostname = hostname.remove(seperatedBy: .whitespacesAndNewlines) session.port = port session.connectionType = connectionType + if logging { session.connectionLogger = parseLog } + if withAuthType, let auth = authType { session.authType = auth if session.authType == MCOAuthType.xoAuth2 { - session.oAuth2Token = EmailHelper.singleton().authorization?.authState.lastTokenResponse?.accessToken + session.oAuth2Token = EmailHelper + .singleton() + .authorization? + .authState + .lastTokenResponse? + .accessToken } } + if credentials { session.username = username.lowercased() session.password = password } + return session } - func createIMAPSession(logging: Bool = true, credentials: Bool = false, withAuthType: Bool = true) -> MCOIMAPSession? { + func createIMAPSession(logging: Bool = true, + credentials: Bool = false, + withAuthType: Bool = true) -> MCOIMAPSession? { loggerCalled = false + guard sessionType == .IMAP else { return nil } + let session = MCOIMAPSession() session.timeout = TimeInterval(MailServer.maxWaitingSeconds) - session.hostname = hostname.remove(seperatedBy: .whitespacesAndNewlines) session.port = port session.connectionType = connectionType + if logging { session.connectionLogger = parseLog } + if withAuthType, let auth = authType { session.authType = auth if session.authType == MCOAuthType.xoAuth2 { - session.oAuth2Token = EmailHelper.singleton().authorization?.authState.lastTokenResponse?.accessToken + session.oAuth2Token = EmailHelper + .singleton() + .authorization? + .authState + .lastTokenResponse? + .accessToken } } + if credentials { session.username = username session.password = password } + return session } func findHost() -> Bool{ - guard LetterboxModel.currentReachabilityStatus != .notReachable else { + guard LetterboxModel.currentReachabilityStatus != .notReachable else { sendCallback = true self.callback(MailServerConnectionError.NoInternetconnection, self) return false } + toTestConnType.remove(connectionType) if sessionType == SessionType.IMAP { return findIMAPHost() - } - else { + } else { if self.possibleAuthTypes.isEmpty { self.possibleAuthTypes = MailSession.AUTHTYPE } @@ -330,8 +421,7 @@ class MailServer: Comparable { if let newType = self.toTestConnType.popFirst() { self.connectionType = newType _ = self.findHost() - } - else if self.toTestConnType.isEmpty && !self.sendCallback { + } else if self.toTestConnType.isEmpty && !self.sendCallback { self.sendCallback = true self.callback(MailServerConnectionError.ImapSetupError, self) } @@ -351,12 +441,16 @@ class MailServer: Comparable { } private func findSMTPHost() -> Bool { - guard let session = createSMTPSession(logging: true, credentials: true, withAuthType: true) else { + guard let session = createSMTPSession(logging: true, + credentials: true, + withAuthType: true) else { return false } + guard let op = session.loginOperation() else { return false } + op.start({[unowned self] (error: Error?) -> () in guard error == nil else { self.failedAttempts = self.failedAttempts + 1 @@ -364,8 +458,7 @@ class MailServer: Comparable { let serverError = MailServerConnectionError.findErrorCode(error: error) if self.sendCallback(session: session, error: serverError) { self.callback(MailServerConnectionError.SmtpSetupError, self) - } - else { + } else { if !self.findSMTPHost() { self.callback(MailServerConnectionError.SmtpSetupError, self) } @@ -391,7 +484,10 @@ class MailServer: Comparable { return true } switch error { - case .AuthenticationError, .AuthenticationRequiredError, .CertificateError, .GmailIMAPNotEnabledError: + case .AuthenticationError, + .AuthenticationRequiredError, + .CertificateError, + .GmailIMAPNotEnabledError: return true default: if toTestServers.count == 0 && createdTestServers { @@ -406,7 +502,6 @@ class MailServer: Comparable { self.authType = next.authTyp self.connectionType = next.connType return false - } } @@ -417,7 +512,7 @@ class MailServer: Comparable { } createdTestServers = true } - + private func parseLog(logger: Any, type: MCOConnectionLogType, data: Data?) { self.loggerCalled = true @@ -441,7 +536,9 @@ class MailServer: Comparable { if let auth = possibleAuthTypes.last { possibleAuthTypes.removeLast() self.authType = auth - if let session = createIMAPSession(logging: true, credentials: false, withAuthType: true) { + if let session = createIMAPSession(logging: true, + credentials: false, + withAuthType: true) { if let x = session.connectOperation(){ x.start({[unowned self](error: Error?) -> () in guard error == nil else { @@ -470,18 +567,17 @@ class MailServer: Comparable { self.authType = self.possibleAuthTypes.removeLast() self.testUsernameAndPW() return - } - else if conError != MailServerConnectionError.AuthenticationError && (!self.receivedAuthTypes || !self.possibleAuthTypes.isEmpty) && !self.possibleAuthTypes.isEmpty{ + } else if conError != MailServerConnectionError.AuthenticationError + && (!self.receivedAuthTypes || !self.possibleAuthTypes.isEmpty) + && !self.possibleAuthTypes.isEmpty { self.authType = self.possibleAuthTypes.removeLast() self.testUsernameAndPW() return - } - else if !self.sendCallback { + } else if !self.sendCallback { self.sendCallback = true self.callback(conError, self) } - } - else { + } else { self.works = true if !self.sendCallback { self.sendCallback = true @@ -499,13 +595,24 @@ class MailSession { static let SMTPPREFIX = ["smtp", "mail", "outgoing", "", "mx"] static let IMAPPREFIX = ["imap", "mail", "", "mx"] static let IMAPPORT: [UInt32] = [993, 143] - static let AUTHTYPE = [MCOAuthType.saslPlain, MCOAuthType.saslLogin, MCOAuthType.SASLNTLM, MCOAuthType.saslKerberosV4, MCOAuthType.SASLCRAMMD5, MCOAuthType.SASLDIGESTMD5, MCOAuthType.SASLGSSAPI, MCOAuthType.SASLSRP, MCOAuthType.init(rawValue: 0)] - static let CONNTECTIONTYPE = [MCOConnectionType.TLS, MCOConnectionType.startTLS] // We do not test for plain connections! + static let AUTHTYPE = [MCOAuthType.saslPlain, + MCOAuthType.saslLogin, + MCOAuthType.SASLNTLM, + MCOAuthType.saslKerberosV4, + MCOAuthType.SASLCRAMMD5, + MCOAuthType.SASLDIGESTMD5, + MCOAuthType.SASLGSSAPI, + MCOAuthType.SASLSRP, + MCOAuthType.init(rawValue: 0)] + // We do not test for plain connections! + static let CONNTECTIONTYPE = [MCOConnectionType.TLS, MCOConnectionType.startTLS] var defaultIMAPSession: MCOIMAPSession? { get { if let server = try? MailServer(userValues: sessionType, callback: callback) { - if let session = server.createIMAPSession(logging: false, credentials: true, withAuthType: true) { + if let session = server.createIMAPSession(logging: false, + credentials: true, + withAuthType: true) { return session } } @@ -515,8 +622,11 @@ class MailSession { var defaultSMTPSession: MCOSMTPSession? { get { - if let server = try? MailServer(userValues: sessionType, callback: callback) , let session = server.createSMTPSession(logging: false, credentials: true, withAuthType: true){ - + if let server = try? MailServer(userValues: sessionType, + callback: callback), + let session = server.createSMTPSession(logging: false, + credentials: true, + withAuthType: true) { return session } return nil @@ -528,7 +638,7 @@ class MailSession { var username: String var password: String var errors: Set<MailServerConnectionError> = Set() - + private var possibleServers: [MailServer] = [] private var counter = -1 private var success = false @@ -555,40 +665,45 @@ class MailSession { return server } } - return MailServer(defaultValues: sessionType, mailAddr: mailAddr, username: username, password: password, callback: callback) + return MailServer(defaultValues: sessionType, + mailAddr: mailAddr, + username: username, + password: password, + callback: callback) } } - - - init(configSession sessionType: SessionType, mailAddress: String, password: String, username: String?) { + init(configSession sessionType: SessionType, + mailAddress: String, + password: String, + username: String?) { self.sessionType = sessionType self.mailAddr = mailAddress self.password = password if let name = username, !name.isEmpty { self.username = name - } - else { + } else { self.username = mailAddr } } init(loadUserDefaults sessionType: SessionType) throws{ self.sessionType = sessionType + if let username = UserManager.loadUserValue(Attribute.accountname) as? String { self.username = username - } - else if let username = UserManager.loadUserValue(Attribute.userAddr) as? String { + } else if let username = UserManager.loadUserValue(Attribute.userAddr) as? String { self.username = username - } - else { + } else { throw MailServerConnectionError.AuthenticationError } + if let pw = UserManager.loadUserValue(Attribute.userPW) as? String { self.password = pw - }else { + } else { throw MailServerConnectionError.AuthenticationError } + if let addr = UserManager.loadUserValue(Attribute.userAddr) as? String { mailAddr = addr } else { @@ -600,9 +715,8 @@ class MailSession { listeners.append(listener) } - /* - Stores a working server configuration to user defaults. - */ + + /// Stores a working server configuration to user defaults. func storeToUserDefaults() -> Bool { workingServers.sort() workingServers.reverse() @@ -627,7 +741,14 @@ class MailSession { if let rawValue = authType { auth = MCOAuthType.init(rawValue: rawValue) } - let server = MailServer(sessionType: sessionType, username: username, password: password, hostname: hostname, port: port, connectionType: MCOConnectionType.init(rawValue: connType), authType: auth, callback: callback) + let server = MailServer(sessionType: sessionType, + username: username, + password: password, + hostname: hostname, + port: port, + connectionType: MCOConnectionType.init(rawValue: connType), + authType: auth, + callback: callback) possibleServers.append(server) } @@ -635,18 +756,20 @@ class MailSession { return searchForServerConfig(hostFromAdr: false) } - /* - We test all common server configurations (common prefixe x common ports x {TLS, startTLS} ). This may take some time. - */ + // We test all common server configurations (common prefixe x common ports x {TLS, startTLS} ). + // This may take some time. func startLongSearchOfServerConfig(hostFromAdr: Bool) -> Bool { // Try to find a possible config let (_, server) = MailSession.splitAddr(userAddr: self.mailAddr) if let domain = server { if self.sessionType == SessionType.IMAP { - createServers(domain: domain, prefixes: MailSession.IMAPPREFIX, ports: [UInt32(111)]) - } - else { - createServers(domain: domain, prefixes: MailSession.SMTPPREFIX, ports: MailSession.SMTPPORT) + createServers(domain: domain, + prefixes: MailSession.IMAPPREFIX, + ports: [UInt32(111)]) + } else { + createServers(domain: domain, + prefixes: MailSession.SMTPPREFIX, + ports: MailSession.SMTPPORT) } } return searchForServerConfig(hostFromAdr: hostFromAdr) @@ -678,14 +801,23 @@ class MailSession { possibleServers = [] for prefix in prefixes { for port in ports { - // We check both connection types in parallel - // because it takes a long time if we start with the wrong conntection type (TLS or startTLS) + // We check both connection types in parallel because it takes a long + // time if we start with the wrong conntection type (TLS or startTLS) for conType in MailSession.CONNTECTIONTYPE { - var hostname = prefix+"."+domain + var hostname = prefix + "." + domain + if prefix.isEmpty { hostname = domain } - let server = MailServer(sessionType: sessionType, username: username, password: password, hostname: hostname, port: port, connectionType: conType, authType: nil, callback: callback) + + let server = MailServer(sessionType: sessionType, + username: username, + password: password, + hostname: hostname, + port: port, + connectionType: conType, + authType: nil, + callback: callback) possibleServers.append(server) } } @@ -702,13 +834,12 @@ class MailSession { } private func callback(error: MailServerConnectionError?, server: MailServer) { - DispatchQueue.main.async(execute:{ + DispatchQueue.main.async { if error == nil { self.success = true self.workingServers.append(server) self.notifyListener() - } - else { + } else { if let error = error { self.errors.insert(error) } @@ -718,7 +849,7 @@ class MailSession { self.notifyListener() } } - }) + } } private func readJson() -> [MCONetService] { @@ -729,8 +860,7 @@ class MailSession { if let provider = manager.provider(forEmail: mailAddr){ if sessionType == SessionType.IMAP { servers = provider.imapServices() - } - else { + } else { servers = provider.smtpServices() } } @@ -785,7 +915,7 @@ extension MCOConnectionType: Equatable, Hashable { } } - static func parseConnType(msg: String) -> MCOConnectionType?{ + static func parseConnType(msg: String) -> MCOConnectionType? { /* RFC 3501 (IMAP): See: https://tools.ietf.org/html/rfc3501#section-6.1.1 @@ -794,7 +924,7 @@ extension MCOConnectionType: Equatable, Hashable { if msg.contains("starttls".lowercased()) { return MCOConnectionType.startTLS } - else if msg.contains("tls".lowercased()) && msg.contains("CAPABILITY".lowercased()){ + else if msg.contains("tls".lowercased()) && msg.contains("CAPABILITY".lowercased()) { return MCOConnectionType.TLS } return nil @@ -842,10 +972,8 @@ extension MCOAuthType: Comparable { } static func parseAuthType(msg: String) -> [MCOAuthType] { - /* - RFC 3501 (IMAP): - See: https://tools.ietf.org/html/rfc3501#section-6.1.1 - */ + // RFC 3501 (IMAP): + // See: https://tools.ietf.org/html/rfc3501#section-6.1.1 let msg = msg.lowercased() var authTypes: [MCOAuthType] = [] if msg.contains("CAPABILITY".lowercased()) { // IMAP @@ -861,23 +989,23 @@ extension MCOAuthType: Comparable { if msg.contains("AUTH=GSSAPI".lowercased()) { authTypes.append(MCOAuthType.SASLGSSAPI) } - if msg.contains("AUTH=Kerberos".lowercased()){ + if msg.contains("AUTH=Kerberos".lowercased()) { authTypes.append(MCOAuthType.saslKerberosV4) } - if msg.contains("AUTH=LSRP".lowercased()){ + if msg.contains("AUTH=LSRP".lowercased()) { authTypes.append(MCOAuthType.SASLSRP) } - if msg.contains("AUTH=LNTLM".lowercased()){ + if msg.contains("AUTH=LNTLM".lowercased()) { authTypes.append(MCOAuthType.SASLNTLM) } - if msg.contains("AUTH=CRAMMD5".lowercased()){ + if msg.contains("AUTH=CRAMMD5".lowercased()) { authTypes.append(MCOAuthType.SASLCRAMMD5) } - if msg.contains("AUTH=DIGESTMD5".lowercased()){ + if msg.contains("AUTH=DIGESTMD5".lowercased()) { authTypes.append(MCOAuthType.SASLDIGESTMD5) } - } - else if msg.contains("250-AUTH".lowercased()) { // SMTP + } else if msg.contains("250-AUTH".lowercased()) { + // SMTP if msg.contains("XOAUTH2".lowercased()) { authTypes.append(MCOAuthType.xoAuth2) } @@ -890,19 +1018,19 @@ extension MCOAuthType: Comparable { if msg.contains("GSSAPI".lowercased()) { authTypes.append(MCOAuthType.SASLGSSAPI) } - if msg.contains("Kerberos".lowercased()){ + if msg.contains("Kerberos".lowercased()) { authTypes.append(MCOAuthType.saslKerberosV4) } - if msg.contains("LSRP".lowercased()){ + if msg.contains("LSRP".lowercased()) { authTypes.append(MCOAuthType.SASLSRP) } - if msg.contains("LNTLM".lowercased()){ + if msg.contains("LNTLM".lowercased()) { authTypes.append(MCOAuthType.SASLNTLM) } - if msg.contains("CRAMMD5".lowercased()){ + if msg.contains("CRAMMD5".lowercased()) { authTypes.append(MCOAuthType.SASLCRAMMD5) } - if msg.contains("DIGESTMD5".lowercased()){ + if msg.contains("DIGESTMD5".lowercased()) { authTypes.append(MCOAuthType.SASLDIGESTMD5) } } diff --git a/enzevalos_iphone/PGP/SwiftPGP.swift b/enzevalos_iphone/PGP/SwiftPGP.swift index 1a7ece17..64ad54fe 100644 --- a/enzevalos_iphone/PGP/SwiftPGP.swift +++ b/enzevalos_iphone/PGP/SwiftPGP.swift @@ -367,14 +367,14 @@ class SwiftPGP: Encryption { passcode = generatePW(size: PasscodeSize, splitInBlocks: true) } exportPwKeyChain[id] = passcode - if let message = armoredKey.data(using: .utf8) { - /* - if let cipher = try? ObjectivePGP.symmetricEncrypt(message, signWith: nil, encryptionKey: passcode, passphrase: passcode, armored: false){ - let armorMessage = Armor.armored(cipher, as: PGPArmorType.message) - return armorMessage - } - */ - } +// if let message = armoredKey.data(using: .utf8) { +// +// if let cipher = try? ObjectivePGP.symmetricEncrypt(message, signWith: nil, encryptionKey: passcode, passphrase: passcode, armored: false){ +// let armorMessage = Armor.armored(cipher, as: PGPArmorType.message) +// return armorMessage +// } +// +// } return nil } return armoredKey @@ -384,13 +384,20 @@ class SwiftPGP: Encryption { } func importKey(_ passcode: String, key: String) -> [String] { - if let keyData = try? Armor.readArmored(key) { - /* - if let plaintext = try? ObjectivePGP.symmetricDecrypt(keyData, key: passcode, verifyWith: nil, signed: nil, valid: nil, integrityProtected: nil), let ids = try? importKeys(data: plaintext, pw: nil, secret: true) { - return ids - } - */ - } +// if let keyData = try? Armor.readArmored(key) { +// if let plaintext = try? ObjectivePGP +// .symmetricDecrypt(keyData, +// key: passcode, +// verifyWith: nil, +// signed: nil, +// valid: nil, +// integrityProtected: nil), +// let ids = try? importKeys(data: plaintext, +// pw: nil, +// secret: true) { +// return ids +// } +// } return [] } @@ -817,39 +824,45 @@ class SwiftPGP: Encryption { if let p = password{ pw = p } - var chiphers = [String]() + let chiphers = [String]() - for text in textToEncrypt{ - if let data = text.data(using: .utf8){ - /* - if let chipher = try? ObjectivePGP.symmetricEncrypt(data, signWith: nil, encryptionKey: password, passphrase: pw, armored: false){ - if armored{ - chiphers.append(Armor.armored(chipher, as: PGPArmorType.message)) - } - else{ - chiphers.append(chipher.base64EncodedString(options: .init(arrayLiteral: .lineLength76Characters, .endLineWithLineFeed))) - } - } - */ - } - } +// for text in textToEncrypt { +// if let data = text.data(using: .utf8) { +// if let chipher = try? ObjectivePGP +// .symmetricEncrypt(data, +// signWith: nil, +// encryptionKey: password, +// passphrase: pw, +// armored: false) { +// if armored { +// chiphers.append(Armor.armored(chipher, as: PGPArmorType.message)) +// } else { +// chiphers.append(chipher.base64EncodedString(options: .init(arrayLiteral: .lineLength76Characters, .endLineWithLineFeed))) +// } +// } +// } +// } return (chiphers, pw) } func symmetricDecrypt(chipherTexts: [String], password: String) -> [String]{ - var plaintexts = [String]() + let plaintexts = [String]() - for chipher in chipherTexts{ - if let data = chipher.data(using: .utf8){ - /* - if let plainData = try? ObjectivePGP.symmetricDecrypt(data, key: password, verifyWith: nil, signed: nil, valid: nil, integrityProtected: nil){ - if let plainText = String(data: plainData, encoding: .utf8){ - plaintexts.append(plainText) - } - } - */ - } - } +// for chipher in chipherTexts { +// if let data = chipher.data(using: .utf8) { +// if let plainData = try? ObjectivePGP +// .symmetricDecrypt(data, +// key: password, +// verifyWith: nil, +// signed: nil, +// valid: nil, +// integrityProtected: nil) { +// if let plainText = String(data: plainData, encoding: .utf8){ +// plaintexts.append(plainText) +// } +// } +// } +// } return plaintexts } } diff --git a/enzevalos_iphone/SwiftUI/Compose/ComposeModel.swift b/enzevalos_iphone/SwiftUI/Compose/ComposeModel.swift index 7d3dae27..1b8c21ae 100644 --- a/enzevalos_iphone/SwiftUI/Compose/ComposeModel.swift +++ b/enzevalos_iphone/SwiftUI/Compose/ComposeModel.swift @@ -34,8 +34,9 @@ class ComposeModel: ObservableObject { /// Adds email addresses to given RecipientFieldModel. /// - /// - Parameter addresses: String array of email addresses to add. - /// - Parameter model: RecipientFieldModel to which to add the addresses. + /// - Parameters: + /// - addresses: String array of email addresses to add. + /// - model: RecipientFieldModel to which to add the addresses. private func addAddresses(_ addresses: [String], model: RecipientFieldModel) { for address in addresses { model.addNewAddress(address) @@ -43,6 +44,8 @@ class ComposeModel: ObservableObject { } /// Generates OutgoingMail with given email contents. + /// + /// - Returns: Outgoing mail filled out with relevant information from RecipientsModel. private func generateMail() -> OutgoingMail { OutgoingMail(toAddresses: recipientsModel.toEMails, ccAddresses: recipientsModel.ccEMails, diff --git a/enzevalos_iphone/SwiftUI/Compose/ComposeView.swift b/enzevalos_iphone/SwiftUI/Compose/ComposeView.swift index b95ef946..74b57b46 100644 --- a/enzevalos_iphone/SwiftUI/Compose/ComposeView.swift +++ b/enzevalos_iphone/SwiftUI/Compose/ComposeView.swift @@ -48,94 +48,14 @@ struct ComposeView: View { } .padding() .animation(.default) - Spacer() } } -struct CcAndBccFields: View { - @ObservedObject var model: RecipientsModel - - var body: some View { - VStack { - RecipientField(model: model.ccModel) - - Divider() - - if model.showBccField { - RecipientField(model: model.bccModel) - - Divider() - } - } - } -} - -/// A view in which recipients get added or removed. -struct RecipientField: View { - @ObservedObject var model: RecipientFieldModel - @State var showList = false - - var body: some View { - VStack { - HStack { - // Recipient text field - Text(model.type.asString) - .foregroundColor(Color(UIColor.tertiaryLabel)) - - // Shows selected recipients as blue capsules - // followed by TextField for new recipients - ScrollView(.horizontal) { - HStack(spacing: 4) { - ForEach(model.selectedContacts) { (recipient: AddressRecord) in - Text(recipient.displayname ?? recipient.email) - .foregroundColor(.white) - .padding(.horizontal, 12) - .padding(.vertical, 2) - .background(Color.accentColor) - .clipShape(Capsule()) - } - TextField("", text: $model.text) { isEditing in - model.parent?.isEditingCcOrBcc = isEditing - } onCommit: { - model.commitText() - // TODO: Fix bug on first Cc or Bcc recipient commit - // For some reason, model.selectedContacts.count stays 0 - // after the first committed recipient, leading to the Cc - // and Bcc fields getting collapsed again despite the - // first recipient clearly getting rendered as a blue - // capsule in the ForEach loop above. 🤔🤔🤔 - print(model.selectedContacts.count) - } - .frame(minWidth: 200) - .autocapitalization(.none) - .keyboardType(.emailAddress) - } - } - - // Toggles contact list - if model.type != .ccBcc { - Button(action: { showList.toggle() }) { - Image(systemName: !showList ? "plus.circle" : "chevron.up.circle") - } - } - } - .frame(height: 20) - - // Contact list - if showList { - Divider() - RecipientListView() - .frame(height: 460) - } - } - .environmentObject(model) - } -} - -/// A view containing the Cancel and Send buttons for an email. +/// A view that contains the Cancel and Send buttons for an email. struct ComposeViewHeader: View { @Environment(\.presentationMode) var presentationMode @EnvironmentObject var model: ComposeModel + @State var encryptionOn = true var body: some View { VStack { @@ -152,6 +72,13 @@ struct ComposeViewHeader: View { Spacer() + // Toggle("Use Encryption", isOn: $encryptionOn) + // .foregroundColor(.accentColor) + // .labelsHidden() + // .position(x: geometry.size.width / 3) + // + // Spacer() + // Send button Button { model.sendMail() @@ -207,6 +134,140 @@ struct UnencryptedSendButton: View { } } +/// A view in which recipients get added or removed. +struct RecipientField: View { + @ObservedObject var model: RecipientFieldModel + @State var showList = false + @State var indexOfSelected: Int? + + var body: some View { + VStack { + HStack { + // Recipient text field + Text(model.type.asString) + .foregroundColor(Color(UIColor.tertiaryLabel)) + + // Selected recipients as blue capsules, + // followed by TextField for new recipients. + ScrollView(.horizontal) { + HStack(spacing: 3) { + CommittedRecipients + NewRecipientTextField + } + } + + // Toggles contact list + if model.type != .ccBcc { + Button(action: { showList.toggle() }) { + Image(systemName: !showList ? "plus.circle" : "chevron.up.circle") + } + } + } + .frame(height: 20) + + // Contact list + if showList || !model.suggestions.isEmpty { + Divider() + RecipientListView() + .frame(maxHeight: .infinity) + .environmentObject(model) + } + } + } + + /// A view that lists all recipients that have been committed to this RecipientField. + var CommittedRecipients: some View { + ForEach(model.selectedContacts) { (recipient: AddressRecord) in + let isSelected = model.selectedContacts.firstIndex(of: recipient) == indexOfSelected + + RecipientCapsule(recipient: recipient, + isSelected: isSelected, + model: model, + indexOfSelected: $indexOfSelected) + .onTapGesture { + indexOfSelected = isSelected ? nil : model.selectedContacts + .firstIndex(of: recipient) + } + } + } + + /// A TextField into which new recipients can be entered. + var NewRecipientTextField: some View { + TextField("", text: $model.text) { isEditing in + model.parent?.isEditingCcOrBcc = isEditing + } onCommit: { + model.commitText() + // TODO: Fix bug on first Cc or Bcc recipient commit + // For some reason, model.selectedContacts.count stays 0 + // after the first committed recipient, leading to the Cc + // and Bcc fields getting collapsed again despite the + // first recipient clearly getting rendered as a blue + // capsule in the ForEach loop above. 🤔🤔🤔 + print(model.selectedContacts.count) + } + .frame(minWidth: 200) + .autocapitalization(.none) + .keyboardType(.emailAddress) + } +} + +/// A view that shows either a single "Cc/Bcc" field or separate "Cc" and "Bcc" fields. +struct CcAndBccFields: View { + @ObservedObject var model: RecipientsModel + + var body: some View { + VStack { + RecipientField(model: model.ccModel) + + Divider() + + if model.showBccField { + RecipientField(model: model.bccModel) + Divider() + } + } + } +} + +/// A view that shows a recipient committed to a recipient field. +struct RecipientCapsule: View { + var recipient: AddressRecord + var isSelected: Bool + @ObservedObject var model: RecipientFieldModel + @Binding var indexOfSelected: Int? + + var body: some View { + HStack { + Text(name) + + if isSelected { + Button(action: removeRecipient) { + Image(systemName: "xmark") + .font(.caption2) + } + } + } + .foregroundColor(isSelected ? .white : .accentColor) + .padding(.horizontal, 12) + .padding(.vertical, 2) + .background(Capsule().fill(isSelected ? Color.accentColor : Color(UIColor.tertiaryLabel))) + } + + // TODO: Use actual displayname once it's implemented. + // Currently just uses first part of email before "@". + var name: String { + recipient.displayname ?? recipient.email.components(separatedBy: "@")[0] + } + + func removeRecipient() { + if let index = model.selectedContacts.firstIndex(of: recipient) { + model.deselectContact(at: index) + indexOfSelected = nil + } + } +} + +/// Canvas Preview struct ComposeView_Previews: PreviewProvider { static var previews: some View { ComposeView() diff --git a/enzevalos_iphone/SwiftUI/Compose/RecipientFieldModel.swift b/enzevalos_iphone/SwiftUI/Compose/RecipientFieldModel.swift index c85b7b56..dc4906c7 100644 --- a/enzevalos_iphone/SwiftUI/Compose/RecipientFieldModel.swift +++ b/enzevalos_iphone/SwiftUI/Compose/RecipientFieldModel.swift @@ -35,7 +35,11 @@ class RecipientFieldModel: ObservableObject { // TODO: Show suggestions in ComposeView. if !text.isEmpty { - let frc = dataprovider.createFetchResultController(fetchRequest: AddressRecord.lookForPrefix(prefix: text)) + let frc = dataprovider + .createFetchResultController( + fetchRequest: AddressRecord.lookForPrefix(prefix: text) + ) + if let addresses = frc.fetchedObjects { suggestions = addresses } else { @@ -47,6 +51,29 @@ class RecipientFieldModel: ObservableObject { } } + /// Gets array of contacts sorted by name or recency. + /// + /// - Parameter sortBy: Determines what key the contacts get sorted by. + /// - Returns: Sorted array of contacts. + func getContacts(sortBy: AddressRecord.SortBy) -> [AddressRecord] { + if !suggestions.isEmpty { + return suggestions + } + + let frc = LetterboxModel + .instance + .dataProvider + .createFetchResultController( + fetchRequest: AddressRecord + .all(sortBy: sortBy) + ) + + if let addresses = frc.fetchedObjects { + return addresses + } + return [] + } + /// Adds contact at index to recipients. func selectContact(addr: AddressRecord) { selectedContacts.append(addr) @@ -55,11 +82,11 @@ class RecipientFieldModel: ObservableObject { } /// Removes contact at index from recipients. - func unselectContactAt(i: Int) { - if i < selectedContacts.count { - selectedContacts.remove(at: i) + func deselectContact(at index: Int) { + if index < selectedContacts.count { + selectedContacts.remove(at: index) } else { - print("ERROR wrong index! \(i) but \(selectedContacts.count)") + print("ERROR wrong index! \(index) but \(selectedContacts.count)") } } @@ -79,7 +106,11 @@ class RecipientFieldModel: ObservableObject { } dataprovider.importNewData(from: [AddressProperties(email: address)]) { error in - let frc = self.dataprovider.createFetchResultController(fetchRequest: AddressRecord.lookForExisting(email: address)) + let frc = self + .dataprovider + .createFetchResultController( + fetchRequest: AddressRecord.lookForExisting(email: address) + ) if let addresses = frc.fetchedObjects, let addr = addresses.first { self.selectedContacts.append(addr) diff --git a/enzevalos_iphone/SwiftUI/Compose/RecipientListView.swift b/enzevalos_iphone/SwiftUI/Compose/RecipientListView.swift index 09897319..f29165df 100644 --- a/enzevalos_iphone/SwiftUI/Compose/RecipientListView.swift +++ b/enzevalos_iphone/SwiftUI/Compose/RecipientListView.swift @@ -12,15 +12,6 @@ struct RecipientListView: View { @EnvironmentObject var model: RecipientFieldModel @State var sortByName = true - // Array of contacts sorted by name or recency. - var contacts: [AddressRecord] { - let frc = LetterboxModel.instance.dataProvider.createFetchResultController(fetchRequest: AddressRecord.all(sortBy: sortByName ? .name : .recency)) - if let addresses = frc.fetchedObjects { - return addresses - } - return [] - } - var body: some View { VStack(alignment: .leading) { // Sorting options @@ -37,7 +28,7 @@ struct RecipientListView: View { // Contact list ScrollView { - ForEach(contacts) { contact in + ForEach(model.getContacts(sortBy: sortByName ? .name : .recency)) { contact in RecipientRowView(contact: contact) Divider() } diff --git a/enzevalos_iphone/SwiftUI/Compose/RecipientRowView.swift b/enzevalos_iphone/SwiftUI/Compose/RecipientRowView.swift index 93b85aca..64bff666 100644 --- a/enzevalos_iphone/SwiftUI/Compose/RecipientRowView.swift +++ b/enzevalos_iphone/SwiftUI/Compose/RecipientRowView.swift @@ -48,7 +48,7 @@ struct RecipientRowView: View { // Adds or removes contact from recipients. Button(action: { if let index = model.selectedContacts.firstIndex(where: { $0.email == contact.email }) { - model.unselectContactAt(i: index) + model.deselectContact(at: index) } else { model.selectContact(addr: contact) } diff --git a/enzevalos_iphone/SwiftUI/Compose/RecipientsModel.swift b/enzevalos_iphone/SwiftUI/Compose/RecipientsModel.swift index 04b61012..c89fa559 100644 --- a/enzevalos_iphone/SwiftUI/Compose/RecipientsModel.swift +++ b/enzevalos_iphone/SwiftUI/Compose/RecipientsModel.swift @@ -85,6 +85,7 @@ class RecipientsModel: ObservableObject { } } + /// Updates whether to display "Bcc" field based on state of recipient fields. func updateShowBccField() { showBccField = isEditingCcOrBcc || !ccModel.selectedContacts.isEmpty diff --git a/enzevalos_iphone/SwiftUI/Inbox/InboxView.swift b/enzevalos_iphone/SwiftUI/Inbox/InboxView.swift index 21718661..d24de44c 100644 --- a/enzevalos_iphone/SwiftUI/Inbox/InboxView.swift +++ b/enzevalos_iphone/SwiftUI/Inbox/InboxView.swift @@ -9,39 +9,48 @@ import SwiftUI import CoreData // TODO: Refactor to Model! -// Updating text -> Last update, updating, no connection.... +// Updating text -> Last update, updating, no connection... struct InboxView: View { var folderpath: String var name: String @State var goToFolders = false - + var body: some View { NavigationView{ MailListView(folderpath: folderpath, name: name) - .environment(\.managedObjectContext, PersistentDataProvider.dataProvider.persistentContainer.viewContext) + .environment(\.managedObjectContext, + PersistentDataProvider + .dataProvider + .persistentContainer + .viewContext) .navigationBarItems(leading: self.folderButton, trailing: keyManagementButton) } } private var keyManagementButton: some View { - NavigationLink( - destination: KeyManagementOverview(), - label: { - Image(systemName: "slider.horizontal.3").imageScale(.large) - }) + NavigationLink(destination: KeyManagementOverview()) { + Image(systemName: "slider.horizontal.3") + .imageScale(.large) + } } private var folderButton: some View { - Button(action:{ + Button(action: { goToFolders = true - }, label: { - Image(systemName: "tray.2").imageScale(.large) - }) + }) { + Image(systemName: "tray.2") + .imageScale(.large) + } .background( NavigationLink(destination: FolderOverView() - .environment(\.managedObjectContext, PersistentDataProvider.dataProvider.persistentContainer.viewContext), isActive: $goToFolders) { + .environment(\.managedObjectContext, + PersistentDataProvider + .dataProvider + .persistentContainer + .viewContext), + isActive: $goToFolders) { EmptyView() } .hidden() @@ -55,6 +64,10 @@ struct InboxView: View { struct InboxView_Previews: PreviewProvider { static var previews: some View { return InboxView(folderpath: "INBOX", name: "INBOX") - .environment(\.managedObjectContext, PersistentDataProvider.proxyPersistentDataProvider.persistentContainer.viewContext) + .environment(\.managedObjectContext, + PersistentDataProvider + .proxyPersistentDataProvider + .persistentContainer + .viewContext) } } diff --git a/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift b/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift index c88f1a30..d0f58a6b 100644 --- a/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift +++ b/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift @@ -35,9 +35,9 @@ struct MailListView: View { mainView .navigationBarTitle(name, displayMode: .inline) - .onAppear(perform: { + .onAppear { self.updateMails() - }) + } .sheet(isPresented: $composeMail) { ComposeView() } @@ -61,7 +61,7 @@ struct MailListView: View { } private var mailList: some View { - List (self.mails.filter(filterKeyRecord), id: \.self){ record in + List (self.mails.filter(filterKeyRecord), id: \.self) { record in NavigationLink( destination: ReadMainView(model: ReadModel(mail: record))) { MailRow(mail: record) @@ -69,17 +69,18 @@ struct MailListView: View { } .resignKeyboardOnDragGesture() // hide keyboard when dragging } - + private var composeButton: some View { Button(action: { composeMail = true - }, label: { + }) { Image(systemName: "square.and.pencil").imageScale(.large) - }) + } } private var lastUpdate: some View { var text = NSLocalizedString("Updating", comment: "updating...") + if !updating { let last = Date() let dateFormatter = DateFormatter() @@ -88,41 +89,52 @@ struct MailListView: View { let dateString = dateFormatter.string(from: last) text = NSLocalizedString("LastUpdate", comment: "") + " " + dateString } - return Button(action: updateMails, label: {Text(text) - .font(.callout) - - }) + + return Button(action: updateMails) { + Text(text) + .font(.callout) + } } func updateMails() { guard !updating else { return } - LetterboxModel.instance.mailHandler.updateFolder(folderpath: folderpath, completionCallback: {error in - if error == nil { - self.updating = false - } - // TODO: Add error message - }) + LetterboxModel + .instance + .mailHandler + .updateFolder(folderpath: folderpath, + completionCallback: { error in + if error == nil { + self.updating = false + } + // TODO: Add error message + }) updating = true } func filterKeyRecord(keyRecord: MailRecord) -> Bool { let searchType = SearchType.findType(i: searchField) - if self.searchText.isEmpty || self.searchText == NSLocalizedString("Searchbar.Title", comment: "Search") { + if self.searchText.isEmpty + || self.searchText == NSLocalizedString("Searchbar.Title", comment: "Search") { return true } + let query = self.searchText.lowercased() - if (searchType == .All || searchType == .Sender) && (containsSearchTerms(content: keyRecord.sender.displayname, searchText: query) || containsSearchTerms(content: keyRecord.sender.email, searchText: query)) { + if (searchType == .All || searchType == .Sender) + && (containsSearchTerms(content: keyRecord.sender.displayname, searchText: query) + || containsSearchTerms(content: keyRecord.sender.email, searchText: query)) { return true - } - else if (searchType == .All || searchType == .Sender) && keyRecord.addresses.filter({containsSearchTerms(content: $0.email, searchText: query)}).count > 0 { + } else if (searchType == .All || searchType == .Sender) + && keyRecord.addresses.filter({ + containsSearchTerms(content: $0.email, + searchText: query) }).count > 0 { return true - } - else if (searchType == .All || searchType == .Subject) && containsSearchTerms(content: keyRecord.subject, searchText: query){ + } else if (searchType == .All || searchType == .Subject) + && containsSearchTerms(content: keyRecord.subject, searchText: query) { return true - } - else if (searchType == .All || searchType == .Body) && containsSearchTerms(content: keyRecord.body, searchText: query){ + } else if (searchType == .All || searchType == .Body) + && containsSearchTerms(content: keyRecord.body, searchText: query) { return true } return false diff --git a/enzevalos_iphone/persistentData/AddressRecord.swift b/enzevalos_iphone/persistentData/AddressRecord.swift index 20828173..5b7e1ab1 100644 --- a/enzevalos_iphone/persistentData/AddressRecord.swift +++ b/enzevalos_iphone/persistentData/AddressRecord.swift @@ -82,9 +82,8 @@ extension AddressRecord { public var name: String { get { - if let contact = self.phoneBookID { - // TODO Look up cn contact name! - } + // TODO: Look up CN contact name! +// if let contact = self.phoneBookID {} if let displayname = self.displayname { return displayname } diff --git a/enzevalos_iphone/persistentData/MailRecord.swift b/enzevalos_iphone/persistentData/MailRecord.swift index c107320e..5cd89e4c 100644 --- a/enzevalos_iphone/persistentData/MailRecord.swift +++ b/enzevalos_iphone/persistentData/MailRecord.swift @@ -205,14 +205,15 @@ extension MailRecord: DisplayMail { } func markAsRead(isRead: Bool) { + + // TODO: FIX! Faults the current objects... +// if let context = self.managedObjectContext { +// var newFlag = self.messageFlag +// newFlag.insert(.seen) +// flag = Int16(newFlag.rawValue) +// try? PersistentDataProvider.dataProvider.save(taskContext: context) // <- later? +// } return - // TODO FIX! Faults the current objects... - if let context = self.managedObjectContext { - var newFlag = self.messageFlag - newFlag.insert(.seen) - flag = Int16(newFlag.rawValue) - try? PersistentDataProvider.dataProvider.save(taskContext: context) // <- later? - } } var transportEnc: Bool { -- GitLab