diff --git a/enzevalos_iphone.xcodeproj/project.pbxproj b/enzevalos_iphone.xcodeproj/project.pbxproj index a9226f7dddb7b33867b09461c133cb9b18b9ef83..262cbaeb67b38fc344adc40c93de0e5952a7319f 100644 --- a/enzevalos_iphone.xcodeproj/project.pbxproj +++ b/enzevalos_iphone.xcodeproj/project.pbxproj @@ -27,6 +27,9 @@ 3EB4FAA420120096001D0625 /* DialogOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EB4FAA320120096001D0625 /* DialogOption.swift */; }; 3EC35F2420037651008BDF95 /* InvitationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EC35F2320037651008BDF95 /* InvitationHelper.swift */; }; 3EC35F302003838E008BDF95 /* InvitationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EC35F2F2003838E008BDF95 /* InvitationTests.swift */; }; + 3FB75DC525FFA75C00919925 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB75DC425FFA75B00919925 /* ComposeView.swift */; }; + 3FB75DC925FFA77800919925 /* RecipientRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB75DC825FFA77800919925 /* RecipientRowView.swift */; }; + 3FB75DCD25FFD37400919925 /* RecipientListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB75DCC25FFD37400919925 /* RecipientListView.swift */; }; 4705E31A25AE36710065FF90 /* SMIMEMailTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4705E31925AE36710065FF90 /* SMIMEMailTest.swift */; }; 4706D65F225B7B6B00B3F1D3 /* ItunesHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4706D65E225B7B6B00B3F1D3 /* ItunesHandler.swift */; }; 4706D661225CD21D00B3F1D3 /* ExportKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4706D660225CD21D00B3F1D3 /* ExportKeyHelper.swift */; }; @@ -169,9 +172,7 @@ 47CEF4EB2052C3C800887CDB /* ObjectivePGP.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 47CEF4EA2052C3C700887CDB /* ObjectivePGP.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 47CEF4ED2052C3E700887CDB /* ObjectivePGP.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47CEF4EC2052C3E600887CDB /* ObjectivePGP.framework */; }; 47D1302B1F7CEE6D007B14DF /* DebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47D1302A1F7CEE6D007B14DF /* DebugSettings.swift */; }; - 47E04DD32559992A00189320 /* SelectReceiverModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E04DD22559992A00189320 /* SelectReceiverModel.swift */; }; - 47E04DD7255C475800189320 /* BodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E04DD6255C475700189320 /* BodyView.swift */; }; - 47E04DDB255C4F8E00189320 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E04DDA255C4F8E00189320 /* ComposeView.swift */; }; + 47E04DD32559992A00189320 /* RecipientsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E04DD22559992A00189320 /* RecipientsModel.swift */; }; 47E04DDF255D3CF600189320 /* ComposeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E04DDE255D3CF600189320 /* ComposeModel.swift */; }; 47E7BE5B22319B6900C8EF94 /* EncMailFromMac.eml in Resources */ = {isa = PBXBuildFile; fileRef = 47E7BE5A22319B6900C8EF94 /* EncMailFromMac.eml */; }; 47E7BE5D22319B7100C8EF94 /* SignedMailFromMac.eml in Resources */ = {isa = PBXBuildFile; fileRef = 47E7BE5C22319B7000C8EF94 /* SignedMailFromMac.eml */; }; @@ -195,10 +196,7 @@ 47F867E42052B49800AA832F /* libbz2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 47F867E32052B49800AA832F /* libbz2.tbd */; }; 47FA8EA8254C7E5B006883D0 /* FolderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FA8EA7254C7E5B006883D0 /* FolderRow.swift */; }; 47FA8EAC254D77DE006883D0 /* MailListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FA8EAB254D77DE006883D0 /* MailListView.swift */; }; - 47FA8EB3254D93F0006883D0 /* SelectReceiverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FA8EB2254D93F0006883D0 /* SelectReceiverView.swift */; }; - 47FA8EBB254D967D006883D0 /* AddressTokenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FA8EBA254D967D006883D0 /* AddressTokenView.swift */; }; - 47FA8EBF254D9CAD006883D0 /* TokenField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FA8EBE254D9CAD006883D0 /* TokenField.swift */; }; - 47FA8EC3254D9E01006883D0 /* TokenFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FA8EC2254D9E01006883D0 /* TokenFieldModel.swift */; }; + 47FA8EC3254D9E01006883D0 /* RecipientFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FA8EC2254D9E01006883D0 /* RecipientFieldModel.swift */; }; 47FAE30E2524AA97005A1BCB /* DataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 47FAE30C2524AA97005A1BCB /* DataModel.xcdatamodeld */; }; 47FAE3122524BFDB005A1BCB /* PersistentDataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FAE3112524BFDB005A1BCB /* PersistentDataError.swift */; }; 47FAE31C2524C07B005A1BCB /* MailRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FAE31B2524C07B005A1BCB /* MailRecord.swift */; }; @@ -303,6 +301,9 @@ 3EB4FAA320120096001D0625 /* DialogOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DialogOption.swift; sourceTree = "<group>"; }; 3EC35F2320037651008BDF95 /* InvitationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InvitationHelper.swift; path = Invitation/InvitationHelper.swift; sourceTree = "<group>"; }; 3EC35F2F2003838E008BDF95 /* InvitationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitationTests.swift; sourceTree = "<group>"; }; + 3FB75DC425FFA75B00919925 /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = "<group>"; }; + 3FB75DC825FFA77800919925 /* RecipientRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientRowView.swift; sourceTree = "<group>"; }; + 3FB75DCC25FFD37400919925 /* RecipientListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipientListView.swift; sourceTree = "<group>"; }; 434D0E988CDFB6D2D46C1EA8 /* Pods_enzevalos_iphoneUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_enzevalos_iphoneUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4705E31925AE36710065FF90 /* SMIMEMailTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMIMEMailTest.swift; sourceTree = "<group>"; }; 4706D65E225B7B6B00B3F1D3 /* ItunesHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItunesHandler.swift; sourceTree = "<group>"; }; @@ -546,9 +547,7 @@ 47CEF4EA2052C3C700887CDB /* ObjectivePGP.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ObjectivePGP.framework; sourceTree = "<group>"; }; 47CEF4EC2052C3E600887CDB /* ObjectivePGP.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ObjectivePGP.framework; path = ../enzevalos_iphone_workspace/ObjectivePGP.framework; sourceTree = "<group>"; }; 47D1302A1F7CEE6D007B14DF /* DebugSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugSettings.swift; sourceTree = "<group>"; }; - 47E04DD22559992A00189320 /* SelectReceiverModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectReceiverModel.swift; sourceTree = "<group>"; }; - 47E04DD6255C475700189320 /* BodyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyView.swift; sourceTree = "<group>"; }; - 47E04DDA255C4F8E00189320 /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = "<group>"; }; + 47E04DD22559992A00189320 /* RecipientsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientsModel.swift; sourceTree = "<group>"; }; 47E04DDE255D3CF600189320 /* ComposeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeModel.swift; sourceTree = "<group>"; }; 47E7BE5A22319B6900C8EF94 /* EncMailFromMac.eml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = EncMailFromMac.eml; sourceTree = "<group>"; }; 47E7BE5C22319B7000C8EF94 /* SignedMailFromMac.eml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SignedMailFromMac.eml; sourceTree = "<group>"; }; @@ -571,10 +570,7 @@ 47F867E32052B49800AA832F /* libbz2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbz2.tbd; path = usr/lib/libbz2.tbd; sourceTree = SDKROOT; }; 47FA8EA7254C7E5B006883D0 /* FolderRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderRow.swift; sourceTree = "<group>"; }; 47FA8EAB254D77DE006883D0 /* MailListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailListView.swift; sourceTree = "<group>"; }; - 47FA8EB2254D93F0006883D0 /* SelectReceiverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectReceiverView.swift; sourceTree = "<group>"; }; - 47FA8EBA254D967D006883D0 /* AddressTokenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressTokenView.swift; sourceTree = "<group>"; }; - 47FA8EBE254D9CAD006883D0 /* TokenField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenField.swift; sourceTree = "<group>"; }; - 47FA8EC2254D9E01006883D0 /* TokenFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenFieldModel.swift; sourceTree = "<group>"; }; + 47FA8EC2254D9E01006883D0 /* RecipientFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientFieldModel.swift; sourceTree = "<group>"; }; 47FAE30D2524AA97005A1BCB /* DataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DataModel.xcdatamodel; sourceTree = "<group>"; }; 47FAE3112524BFDB005A1BCB /* PersistentDataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistentDataError.swift; sourceTree = "<group>"; }; 47FAE31B2524C07B005A1BCB /* MailRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailRecord.swift; sourceTree = "<group>"; }; @@ -1228,14 +1224,12 @@ 47FA8EB1254D93D2006883D0 /* Compose */ = { isa = PBXGroup; children = ( - 47FA8EB2254D93F0006883D0 /* SelectReceiverView.swift */, - 47FA8EBA254D967D006883D0 /* AddressTokenView.swift */, - 47FA8EBE254D9CAD006883D0 /* TokenField.swift */, - 47FA8EC2254D9E01006883D0 /* TokenFieldModel.swift */, - 47E04DD22559992A00189320 /* SelectReceiverModel.swift */, - 47E04DD6255C475700189320 /* BodyView.swift */, - 47E04DDA255C4F8E00189320 /* ComposeView.swift */, + 47FA8EC2254D9E01006883D0 /* RecipientFieldModel.swift */, + 47E04DD22559992A00189320 /* RecipientsModel.swift */, + 3FB75DC825FFA77800919925 /* RecipientRowView.swift */, + 3FB75DCC25FFD37400919925 /* RecipientListView.swift */, 47E04DDE255D3CF600189320 /* ComposeModel.swift */, + 3FB75DC425FFA75B00919925 /* ComposeView.swift */, ); path = Compose; sourceTree = "<group>"; @@ -1883,13 +1877,12 @@ 47BCAF74259FBC810008FE4B /* KeyManagementOverview.swift in Sources */, 4764069A2416B54D00C7D426 /* MailView.swift in Sources */, 479E1BD4253D7E5B009F9D14 /* PermissionModel.swift in Sources */, - 47E04DD32559992A00189320 /* SelectReceiverModel.swift in Sources */, + 47E04DD32559992A00189320 /* RecipientsModel.swift in Sources */, 476406952416B54D00C7D426 /* KeyRecordRow.swift in Sources */, 47B71AB325387AC000CA87C6 /* SearchHelper.swift in Sources */, 47EABF3024240BD300774A93 /* AccountSetupView.swift in Sources */, 47C822602437A143005BCE73 /* CornerRounder.swift in Sources */, 4775D7AC243F18BC0052F2CC /* SecurityBriefingView.swift in Sources */, - 47E04DD7255C475800189320 /* BodyView.swift in Sources */, 971D404A2428C87E002FCD31 /* BadgeCaseView.swift in Sources */, 47C8225B24379EAE005BCE73 /* MessageViewMain.swift in Sources */, 47FAE30E2524AA97005A1BCB /* DataModel.xcdatamodeld in Sources */, @@ -1899,7 +1892,7 @@ 476406992416B54D00C7D426 /* SearchView.swift in Sources */, 47D1302B1F7CEE6D007B14DF /* DebugSettings.swift in Sources */, A5E303D824110F6400310264 /* smime-helpers.c in Sources */, - 47FA8EC3254D9E01006883D0 /* TokenFieldModel.swift in Sources */, + 47FA8EC3254D9E01006883D0 /* RecipientFieldModel.swift in Sources */, 4775D7A8243F0D630052F2CC /* DisplayProtocols.swift in Sources */, F1984D721E1D327200804E1E /* IconsStyleKit.swift in Sources */, 47B71AAE2538354A00CA87C6 /* OnboardingIntro.swift in Sources */, @@ -1923,7 +1916,6 @@ 4706D661225CD21D00B3F1D3 /* ExportKeyHelper.swift in Sources */, 4775D7B02443539E0052F2CC /* DialogProtocols.swift in Sources */, 475B00331F7B9565006CDD41 /* SwiftPGP.swift in Sources */, - 47E04DDB255C4F8E00189320 /* ComposeView.swift in Sources */, 477548E421F77BA0000B22A8 /* StudyParameterProtocol.swift in Sources */, 47BCAF38259CE3930008FE4B /* FavoriteButtonView.swift in Sources */, 47A5D6E42294BFF50084F81D /* Logger.swift in Sources */, @@ -1952,6 +1944,8 @@ 47A2A57223599D180013883D /* FeedbackButtonHelper.swift in Sources */, 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 */, @@ -1961,8 +1955,6 @@ 47C112CA2531E9B000621A07 /* AttachmentRecord.swift in Sources */, 476406982416B54D00C7D426 /* CiricleImage.swift in Sources */, A111F6AD1FA77B170060AFDE /* LoggerDetail.swift in Sources */, - 47FA8EBB254D967D006883D0 /* AddressTokenView.swift in Sources */, - 47FA8EBF254D9CAD006883D0 /* TokenField.swift in Sources */, 47358D92244A5AEA000116D7 /* SelectableTextView.swift in Sources */, 0EF148052422543E00B3C198 /* Certificate.swift in Sources */, 47BCAF5A259DE6770008FE4B /* SecretKeyListView.swift in Sources */, @@ -1976,6 +1968,7 @@ 0EFEF0952417C0B400BB2FF7 /* CHelpers.swift in Sources */, 4764069D2416B54D00C7D426 /* Stroke.swift in Sources */, 47BCAF6C259F9BC50008FE4B /* PasswordView.swift in Sources */, + 3FB75DC525FFA75C00919925 /* ComposeView.swift in Sources */, 478AF715222FD5C600AEF69E /* IncomingMail.swift in Sources */, 47BCAF70259F9E390008FE4B /* PasswordAlert.swift in Sources */, 477120C2254C676000B28C64 /* ContactView.swift in Sources */, @@ -1985,6 +1978,7 @@ 47EABF2D2423C65F00774A93 /* AuthenticationView.swift in Sources */, 47BCAF68259F48840008FE4B /* TempKeyRow.swift in Sources */, 479011492289975D0057AB04 /* NoSecIconStyleKit.swift in Sources */, + 3FB75DC925FFA77800919925 /* RecipientRowView.swift in Sources */, 47C822622438A81C005BCE73 /* MapView.swift in Sources */, 4733B206252B16D100AB5600 /* FolderRecord.swift in Sources */, 71DFE5BA240679E80042019C /* HeaderExtractionValues.swift in Sources */, @@ -1993,7 +1987,6 @@ A18E7D771FBDE5D9002F7CC9 /* LoggingEventType.swift in Sources */, F1984D741E1E92B300804E1E /* LabelStyleKit.swift in Sources */, 47C8225724379EAE005BCE73 /* DialogView.swift in Sources */, - 47FA8EB3254D93F0006883D0 /* SelectReceiverView.swift in Sources */, 47BCAF56259DE23A0008FE4B /* KeyView.swift in Sources */, 478154A721FF3F0900A931EC /* Warning.swift in Sources */, 47E04DDF255D3CF600189320 /* ComposeModel.swift in Sources */, diff --git a/enzevalos_iphone/AddressHandler 2.swift b/enzevalos_iphone/AddressHandler 2.swift new file mode 100644 index 0000000000000000000000000000000000000000..c69264972c14cf9de65f8d969d590d8d7660034c --- /dev/null +++ b/enzevalos_iphone/AddressHandler 2.swift @@ -0,0 +1,329 @@ +// +// AddressHandler.swift +// mail_dynamic_icon_001 +// +// Created by jakobsbode on 14.07.16. +// // This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. +// + +import Foundation +import Contacts +import UIKit.UIImage + +class AddressHandler { + + static var addresses: [String] = [] +/* + static var freqAlgorithm: ([String]) -> [(UIImage, String, String, UIImage?, UIColor)] = { + (inserted: [String]) -> [(UIImage, String, String, UIImage?, UIColor)] in + var cons: [(UIImage, String, String, UIImage?, UIColor)] = [] + do { + + try AppDelegate.getAppDelegate().contactStore.enumerateContacts(with: CNContactFetchRequest(keysToFetch: [CNContactFormatter.descriptorForRequiredKeys(for: CNContactFormatterStyle.fullName), CNContactEmailAddressesKey as CNKeyDescriptor, CNContactImageDataKey as CNKeyDescriptor, CNContactThumbnailImageDataKey as CNKeyDescriptor]), usingBlock: { + (c: CNContact, stop) -> Void in + for email in c.emailAddresses { + let addr = email.value as String + var type: UIImage? = nil + if c.emailAddresses.count > 1 { + if email.label == "_$!<Work>!$_" { + type = UIImage(named: "work2_white")! + } else if email.label == "_$!<Home>!$_" { + type = UIImage(named: "home2_white")! + } else if email.label == "_$!<iCloud>!$_" { + //TODO: appleIcon hinzufügen + } + } + var color = c.getColor() + if c.thumbnailImageData != nil { + color = UIColor.gray //blackColor() + } + if addr == "" { + continue + } + if !inserted.contains(addr.lowercased()) { + if let name = CNContactFormatter.string(from: c, style: .fullName) { + cons.append((c.getImageOrDefault(), name, addr, type, color)) + } else { + cons.append((c.getImageOrDefault(), "NO NAME", addr, type, color)) + } + } + } + }) + } + catch { } + var list: [(UIImage, String, String, UIImage?, UIColor)] = [] + var entrys = CollectionDataDelegate.maxFrequent + if cons.count < entrys { + entrys = cons.count + } + if entrys <= 0 { + return [] + } + for i in 0...entrys - 1 { + //let index = abs(Int(arc4random())) % cons.count + let index = i % cons.count + list.append(cons[index]) + cons.remove(at: index) + } + + return list + } +// TODO + static var freqAlgorithm2: ([String]) -> [(UIImage, String, String, UIImage?, UIColor)] = { + (inserted: [String]) -> [(UIImage, String, String, UIImage?, UIColor)] in + + var cons = DataHandler.handler.folderRecords(folderPath: UserManager.backendInboxFolderPath) + var list: [(UIImage, String, String, UIImage?, UIColor)] = [] + var localInserted = inserted + + for con: KeyRecord in cons { + if list.count >= CollectionDataDelegate.maxFrequent { + break + } + var insertedEntry = false + var address = con.ezContact.getMailAddresses()[0] + for addr in con.ezContact.getMailAddresses() { + if localInserted.contains(addr.mailAddress) { + insertedEntry = true + } + if addr.hasKey { + address = addr + } + } + if !insertedEntry { + var addrType: UIImage? = nil + + if address.label.label == "_$!<Work>!$_" { + addrType = UIImage(named: "work2_white")! + } + if address.label.label == "_$!<Home>!$_" { + addrType = UIImage(named: "home2_white")! + } + if let cn = con.cnContact { + var color = cn.getColor() + if cn.thumbnailImageData != nil { + color = UIColor.gray //blackColor() + } + + var entry = (cn.getImageOrDefault(), con.ezContact.displayname!, address.mailAddress, addrType, color) + + list.append(entry) + localInserted.append(address.mailAddress) + } else { + var entry = (con.ezContact.getImageOrDefault(), con.ezContact.displayname ?? address.mailAddress, address.mailAddress, addrType, con.ezContact.getColor()) + list.append(entry) + localInserted.append(address.mailAddress) + } + } + } + + + + return list + } */ + + static func getContact(_ name: String) -> [CNContact] { + if name == "" { + return [] + } + /*TODO AppDelegate.getAppDelegate().requestForAccess({ access in }) + let authorizationStatus = CNContactStore.authorizationStatus(for: CNEntityType.contacts) + if authorizationStatus == CNAuthorizationStatus.authorized { + do { + let conList = try AppDelegate.getAppDelegate().contactStore.unifiedContacts(matching: CNContact.predicateForContacts(matchingName: name), keysToFetch: [CNContactGivenNameKey as CNKeyDescriptor, CNContactFamilyNameKey as CNKeyDescriptor, CNContactEmailAddressesKey as CNKeyDescriptor, CNContactImageDataKey as CNKeyDescriptor, CNContactThumbnailImageDataKey as CNKeyDescriptor]) + return conList + } catch { + print("exception in contacts get for name: \(name)") + } + } else { + print("no Access!") + } */ + return [] + } + + static func getContactByID(_ identifier: String) -> [CNContact] { + /* AppDelegate.getAppDelegate().requestForAccess({ access in }) + let ids = [identifier] + let authorizationStatus = CNContactStore.authorizationStatus(for: CNEntityType.contacts) + if authorizationStatus == CNAuthorizationStatus.authorized { + do { + let conList = try AppDelegate.getAppDelegate().contactStore.unifiedContacts(matching: CNContact.predicateForContacts(withIdentifiers: ids), keysToFetch: [CNContactGivenNameKey as CNKeyDescriptor, CNContactFamilyNameKey as CNKeyDescriptor, CNContactEmailAddressesKey as CNKeyDescriptor, CNContactImageDataKey as CNKeyDescriptor, CNContactThumbnailImageDataKey as CNKeyDescriptor]) + return conList + } catch { + print("exception in contacts") + } + } else { + print("no Access!") + } */ + return [] + } + + /* [insertedEmail] -> [(contactImage, name, address, emailLabelImage, backgroundcolor)] */ + /* TODO + static func frequentAddresses (_ inserted: [String]) -> [(UIImage, String, String, UIImage?, UIColor)] { + let cons = DataHandler.handler.folderRecords(folderPath: UserManager.backendInboxFolderPath) + var list: [(UIImage, String, String, UIImage?, UIColor)] = [] + var localInserted = inserted + + for con: KeyRecord in cons { + if list.count >= CollectionDataDelegate.maxFrequent { + break + } + var insertedEntry = false + if con.addressNames.isEmpty { + continue + } + var address = con.addresses.first! + if let a = con.ezContact.getMailAddresses().first { + address = a + } + for addr in con.ezContact.getMailAddresses() { + if localInserted.contains(addr.mailAddress) { + insertedEntry = true + } + if addr.hasKey { + address = addr + } + } + if !insertedEntry { + var addrType: UIImage? = nil + + if address.label.label == "_$!<Work>!$_" { + addrType = UIImage(named: "work2_white")! + } + if address.label.label == "_$!<Home>!$_" { + addrType = UIImage(named: "home2_white")! + } + if let cn = con.cnContact { + var color = cn.getColor() + if cn.thumbnailImageData != nil { + color = UIColor.gray //blackColor() + } + let entry = (cn.getImageOrDefault(), con.ezContact.displayname!, address.mailAddress, addrType, color) + + list.append(entry) + localInserted.append(address.mailAddress) + } else { + let entry = (con.ezContact.getImageOrDefault(), con.ezContact.displayname ?? address.mailAddress, address.mailAddress, addrType, con.ezContact.getColor()) + list.append(entry) + localInserted.append(address.mailAddress) + } + } + } + + return list + } +*/ + static func findContact(_ econtact: Contact) -> [CNContact] { + var result = [CNContact]() + if let identifier = econtact.cnContact?.identifier { + // 1. Look up identifier string + result = getContactByID(identifier) + } + if result.count == 0 { + let name = econtact.name.trimmingCharacters(in: .decimalDigits) + // 2. look for name + let query = getContact(name) + for res in query { + if (proveMatching(res, addresses: econtact.getMailAddresses())) { + result.append(res) + } + } + } + if result.count == 0 { + // 3. look for mail addresses + result = contactByEmail(econtact.getMailAddresses()) + } + return result + } + + static func proveMatching(_ result: CNContact, addresses: [MailAddress]) -> Bool { + for email in result.emailAddresses { + for adr in addresses { + let adrRest = email.value as String + if adrRest.lowercased() == adr.email.lowercased() { + return true + } + } + } + return false + } + + static func contactByEmail(_ mailaddreses: [MailAddress]) -> [CNContact] { + var contacts: [CNContact] = [] + + let fetchRequest = CNContactFetchRequest(keysToFetch: [CNContactFormatter.descriptorForRequiredKeys(for: CNContactFormatterStyle.fullName), CNContactEmailAddressesKey as CNKeyDescriptor, CNContactImageDataKey as CNKeyDescriptor, CNContactThumbnailImageDataKey as CNKeyDescriptor]) +/*TODO + do { + try AppDelegate.getAppDelegate().contactStore.enumerateContacts(with: fetchRequest, usingBlock: { (contact, _) in + for addr in mailaddreses { + let contains = Set(contact.emailAddresses.map({ $0.value as String })).contains(addr.email) + if contains { + contacts.append(contact) + } + } + }) + } catch { + print("Problem while accessing contacts by email") + } +*/ + return contacts + } + + + static func updateCNContacts(save: Bool = true) { + let enzContacts = [Contact]() // TODODataHandler.handler.getContacts() + + for contact in enzContacts { + let contacts = findContact(contact) + if contact.cnContact == nil { + if contacts.count > 0 { + // TODO contact.cnContact = contacts.first?.identifier + } + } + else if contacts.count > 0 && contact.cnContact != nil { + //TODO contact.cnContact = nil + } + else if contacts.count > 0 { + let cnContact = contacts.first + if let addresses = cnContact?.getMailAddresses() { + var hasSame = false + for adr1 in contact.getMailAddresses() { + var found = false + for adr2 in addresses { + if adr1.email == adr2.email { + found = true + break + } + } + if found { + hasSame = true + } + } + if !hasSame { + //TODO contact.cnContact = nil + } + } + else { + //TODO contact.cnContact = nil + } + + + } + } + if save { + //TODO DataHandler.handler.save(during: "updateCNContacts") + } + } +} diff --git a/enzevalos_iphone/ContactHandler.swift b/enzevalos_iphone/ContactHandler.swift index 9366e2f0b047b9ef7ad2490af920d459e70d4f0d..35f37d2205e573fbd091dc92667ffb28b13174ff 100644 --- a/enzevalos_iphone/ContactHandler.swift +++ b/enzevalos_iphone/ContactHandler.swift @@ -47,7 +47,7 @@ class ContactHandler { func findContacts(update: Bool) { // TODO: Move to background thread? - if update, let frp = try? PersitentDataProvider.dataProvider.lookUp(entityName: AddressRecord.entityName, sorting: ["email": true], equalPredicates: nil, differentPredicates: ["phoneBookID": "nil"], inPredicates: nil, propertiesToFetch: ["email", "phoneBookID", "displayname", "image"]), let addresses = frp.fetchedObjects as? [AddressRecord]{ // ["phoneBookID": "nil"] + if update, let frp = try? PersistentDataProvider.dataProvider.lookUp(entityName: AddressRecord.entityName, sorting: ["email": true], equalPredicates: nil, differentPredicates: ["phoneBookID": "nil"], inPredicates: nil, propertiesToFetch: ["email", "phoneBookID", "displayname", "image"]), let addresses = frp.fetchedObjects as? [AddressRecord]{ // ["phoneBookID": "nil"] let contacts = self.findContactsBy(ids: addresses.map({($0.phoneBookID ?? "")})) for addr in addresses { var hit = false @@ -71,13 +71,13 @@ class ContactHandler { } } do { - try PersitentDataProvider.dataProvider.save(taskContext: frp.managedObjectContext) + try PersistentDataProvider.dataProvider.save(taskContext: frp.managedObjectContext) } catch { print("Can not store updates!") } } - if let frp = try? PersitentDataProvider.dataProvider.lookUp(entityName: AddressRecord.entityName, sorting: ["email": true], equalPredicates: ["phoneBookID": "nil"], differentPredicates: nil, inPredicates: nil, propertiesToFetch: ["email", "phoneBookID", "displayname", "image"]), let addresses = frp.fetchedObjects as? [AddressRecord]{ // ["phoneBookID": "nil"] + if let frp = try? PersistentDataProvider.dataProvider.lookUp(entityName: AddressRecord.entityName, sorting: ["email": true], equalPredicates: ["phoneBookID": "nil"], differentPredicates: nil, inPredicates: nil, propertiesToFetch: ["email", "phoneBookID", "displayname", "image"]), let addresses = frp.fetchedObjects as? [AddressRecord]{ // ["phoneBookID": "nil"] for addr in addresses { if let contact = self.findContactBy(email: addr.email).first { addr.phoneBookID = contact.identifier @@ -92,7 +92,7 @@ class ContactHandler { } } do { - try PersitentDataProvider.dataProvider.save(taskContext: frp.managedObjectContext) + try PersistentDataProvider.dataProvider.save(taskContext: frp.managedObjectContext) } catch { print("Can not store updates!") } diff --git a/enzevalos_iphone/DebugSettings.swift b/enzevalos_iphone/DebugSettings.swift index a65f4fddad3b22dc9127bdafe2e39843a84f5dd3..563e5587c69289e799a3fe57c66997541783e8d6 100644 --- a/enzevalos_iphone/DebugSettings.swift +++ b/enzevalos_iphone/DebugSettings.swift @@ -21,7 +21,7 @@ import Foundation private let pgp = SwiftPGP() -private let datahandler = PersitentDataProvider.dataProvider +private let datahandler = PersistentDataProvider.dataProvider let SUPPORT_MAIL_ADR = "letterbox@inf.fu-berlin.de" @@ -65,7 +65,7 @@ private func importPublicKey(file: String, type: String, adr: String) { for id in ids { pubKeyProperties.append(PublicKeyProperties(fingerprint: id, cryptoProtocol: .PGP, origin: .FileTransfer, preferEncryption: nil, lastSeenInAutocryptHeader: nil, lastSeenSignedMail: nil, secretKeyProperty: nil, usedAddresses: [AddressProperties(email: adr)])) } - PersitentDataProvider.dataProvider.importNewData(from: pubKeyProperties, completionHandler: {error in print("Import public keys: \(pubKeyProperties.count)")}) + PersistentDataProvider.dataProvider.importNewData(from: pubKeyProperties, completionHandler: {error in print("Import public keys: \(pubKeyProperties.count)")}) } catch _ { } diff --git a/enzevalos_iphone/ItunesHandler.swift b/enzevalos_iphone/ItunesHandler.swift index c9064addd10a80b01c632ded24677504eeda2203..17c268fd58ddabdc8e5bb3fcf11d088b8b96d81c 100644 --- a/enzevalos_iphone/ItunesHandler.swift +++ b/enzevalos_iphone/ItunesHandler.swift @@ -15,7 +15,7 @@ class FileBrowserHandler { static func simpleImportSecretKey() { var sks = fileBrowserHandler.storedKeys.filter{$0.isSecret} // filter known secret keys - if let knownKeyIDs = PersitentDataProvider.dataProvider.fetchedSecretKeyResultsController.fetchedObjects?.map({$0.fingerprint}) { + if let knownKeyIDs = PersistentDataProvider.dataProvider.fetchedSecretKeyResultsController.fetchedObjects?.map({$0.fingerprint}) { sks = sks.filter({!knownKeyIDs.contains($0.keyID)}) } // Import only keys with our mail addr. @@ -36,7 +36,7 @@ class FileBrowserHandler { func extractSecretKeys(withKnownKeys: Bool) -> [TempKey] { var newSecretKeys = storedKeys.filter{$0.isSecret} - if !withKnownKeys, let keys = PersitentDataProvider.dataProvider.fetchedSecretKeyResultsController.fetchedObjects { + if !withKnownKeys, let keys = PersistentDataProvider.dataProvider.fetchedSecretKeyResultsController.fetchedObjects { var knownIDs = keys.map{$0.keyID} knownIDs = knownIDs.filter{return !($0 == "")} newSecretKeys = newSecretKeys.filter{!knownIDs.contains($0.keyID)} @@ -46,7 +46,7 @@ class FileBrowserHandler { func extractPublicKeys(withKnownKeys: Bool) -> [TempKey] { var newPublicKeys = storedKeys.filter{!$0.isSecret} - if !withKnownKeys, let keys = PersitentDataProvider.dataProvider.fetchedPublicKeyResultsController.fetchedObjects { + if !withKnownKeys, let keys = PersistentDataProvider.dataProvider.fetchedPublicKeyResultsController.fetchedObjects { var knownIDs = keys.map{$0.keyID} knownIDs = knownIDs.filter{return !($0 == "")} newPublicKeys = newPublicKeys.filter{!knownIDs.contains($0.keyID)} @@ -115,7 +115,7 @@ class FileBrowserHandler { // Add keyIds in Database let properties = publicKeys.map{PublicKeyProperties(fingerprint: $0.keyID, cryptoProtocol: $0.type, origin: .FileTransfer, preferEncryption: nil, lastSeenInAutocryptHeader: nil, lastSeenSignedMail: nil, secretKeyProperty: nil, usedAddresses: $0.mailAddresses.map{AddressProperties(email: $0)})} - PersitentDataProvider.dataProvider.importNewData(from: properties, completionHandler: {error in print("Import public keys: \(String(describing: error))")}) + PersistentDataProvider.dataProvider.importNewData(from: properties, completionHandler: {error in print("Import public keys: \(String(describing: error))")}) return keyIds } diff --git a/enzevalos_iphone/MailComparison.swift b/enzevalos_iphone/MailComparison.swift index ccddbf54a1ab42ddd1b2d687fb77360aa4382c63..90f348eb32a327ab9caa87465604a3533dc38841 100644 --- a/enzevalos_iphone/MailComparison.swift +++ b/enzevalos_iphone/MailComparison.swift @@ -68,7 +68,7 @@ extension String { looks if the mail address of the sender is already known ***/ public func compareSenderToContacts() -> ResultCompareSenderToContacts { - guard let inboxMails = PersitentDataProvider.dataProvider.fetchedMailResultsController.fetchedObjects else { + guard let inboxMails = PersistentDataProvider.dataProvider.fetchedMailResultsController.fetchedObjects else { let contacts = ContactHandler().findContactBy(email: self) if contacts.count > 0 { return ResultCompareSenderToContacts.isContact diff --git a/enzevalos_iphone/MailHandler.swift b/enzevalos_iphone/MailHandler.swift index 9383d70197f1c04b358538532ac69be7be04664b..ba2ee7a33deb845432bb5b0ec85047392ccc72be 100644 --- a/enzevalos_iphone/MailHandler.swift +++ b/enzevalos_iphone/MailHandler.swift @@ -1,29 +1,27 @@ // -// MailHandler.swift -// mail_dynamic_icon_001 -// -// Created by jakobsbode on 22.08.16. -// // This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. -// - - - -import Foundation -import Contacts -// FIXME: comparison operators with optionals were removed from the Swift Standard Libary. -// Consider refactoring the code to use the non-optional operators. -fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool { + // MailHandler.swift + // mail_dynamic_icon_001 + // + // Created by jakobsbode on 22.08.16. + // // This program is free software: you can redistribute it and/or modify + // it under the terms of the GNU General Public License as published by + // the Free Software Foundation, either version 3 of the License, or + // (at your option) any later version. + // + // This program is distributed in the hope that it will be useful, + // but WITHOUT ANY WARRANTY; without even the implied warranty of + // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + // GNU General Public License for more details. + // + // You should have received a copy of the GNU General Public License + // along with this program. If not, see <https://www.gnu.org/licenses/>. + // + + import Foundation + import Contacts + // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. + // Consider refactoring the code to use the non-optional operators. + fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l < r @@ -32,37 +30,37 @@ fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool { default: return false } -} - -// FIXME: comparison operators with optionals were removed from the Swift Standard Libary. -// Consider refactoring the code to use the non-optional operators. -fileprivate func > <T : Comparable>(lhs: T?, rhs: T?) -> Bool { + } + + // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. + // Consider refactoring the code to use the non-optional operators. + fileprivate func > <T : Comparable>(lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l > r default: return rhs < lhs } -} + } enum SendViewMailSecurityState { case letter case postcard } - -class MailHandler { + + class MailHandler { private static let MAXMAILS = 25 private static let extraHeaders = Autocrypt.EXTRAHEADERS - private let dataProvider = PersitentDataProvider.dataProvider - + private let dataProvider = PersistentDataProvider.dataProvider + static var INBOX: String { return UserManager.backendInboxFolderPath } private var IMAPSes: MCOIMAPSession? var IMAPSession: MCOIMAPSession? { if IMAPSes == nil { - IMAPSes = MailHandler.createIMAPSession() + IMAPSes = MailHandler.createIMAPSession() } return IMAPSes } @@ -77,10 +75,10 @@ class MailHandler { } return nil } - + var shouldTryRefreshOAUTH: Bool { if let imapAuthType = UserManager.loadUserValue(.imapConnectionType) as? Int, - let smtpAuthType = UserManager.loadUserValue(.smtpAuthType) as? Int{ + let smtpAuthType = UserManager.loadUserValue(.smtpAuthType) as? Int{ return (imapAuthType == MCOAuthType.xoAuth2.rawValue || smtpAuthType == MCOAuthType.xoAuth2.rawValue) && !(EmailHelper.singleton().authorization?.authState.isTokenFresh() ?? false) } @@ -158,9 +156,7 @@ class MailHandler { else if let callback = callback{ callback(nil) } - } - - + } // TODO SEND MAIL... later... func storeIMAP(mail: OutgoingMail, folder: String, callback: ((MailServerConnectionError?) -> Void)?) { @@ -206,16 +202,14 @@ class MailHandler { } } - - - func createDraft(_ toEntrys: [String], ccEntrys: [String], bccEntrys: [String], subject: String, message: String, callback: @escaping (MailServerConnectionError?) -> Void) { - let mail = OutgoingMail.createDraft(toEntrys: toEntrys, ccEntrys: ccEntrys, bccEntrys: bccEntrys, subject: subject, textContent: message, htmlContent: nil) + func createDraft(_ toAddresses: [String], ccAddresses: [String], bccAddresses: [String], subject: String, message: String, callback: @escaping (MailServerConnectionError?) -> Void) { + let mail = OutgoingMail.createDraft(toAddresses: toAddresses, ccAddresses: ccAddresses, bccAddresses: bccAddresses, subject: subject, textContent: message, htmlContent: nil) let folder = UserManager.backendDraftFolderPath self.storeIMAP(mail: mail, folder: folder, callback: callback) _ = mail.logMail() } - - func startIMAPIdleIfSupported() throws{ + + func startIMAPIdleIfSupported() throws { if let supported = IMAPIdleSupported { let id = UInt32(0) // TODO FIX!!!! UInt32(DataHandler.handler.findFolder(with: MailHandler.INBOX).maxID) if supported && IMAPIdleSession == nil, let session = MailHandler.createIMAPSession(), let op = session.idleOperation(withFolder: MailHandler.INBOX, lastKnownUID: id){ @@ -226,13 +220,13 @@ class MailHandler { self.errorhandling(error: connerror, originalCall: {do { try self.startIMAPIdleIfSupported() } catch{} - }, completionCallback: nil) + }, completionCallback: nil) return } self.IMAPIdleSession = nil updateFolder(folderpath: MailHandler.INBOX, completionCallback: { error in guard error == nil else { - self.errorhandling(error: error, originalCall: {do { + self.errorhandling(error: error, originalCall: { do { try self.startIMAPIdleIfSupported() } catch{}}, completionCallback: nil) return @@ -243,39 +237,37 @@ class MailHandler { }) } } else { - try checkIdleSupport() + try checkIdleSupport() } } - - private func checkIdleSupport() throws{ + + private func checkIdleSupport() throws { if let session = MailHandler.createIMAPSession(), let op = session.capabilityOperation() { op.start({[unowned self] (error, capabilities) in guard error == nil else { let connerror = MailServerConnectionError.findErrorCode(error: error!) self.errorhandling(error: connerror, originalCall: { - do { - try self.checkIdleSupport() - }catch{}}, completionCallback: nil) + do { + try self.checkIdleSupport() + }catch{}}, completionCallback: nil) return } if let c = capabilities { self.IMAPIdleSupported = c.contains(UInt64(MCOIMAPCapability.idle.rawValue)) do { try self.startIMAPIdleIfSupported() - }catch{} + } catch{} } }) } - } - - + func setFlag(_ uid: UInt64, flags: MCOMessageFlag, folder: String = INBOX) { changeFlag(uid: uid, flags: flags, kind: MCOIMAPStoreFlagsRequestKind.set, folderName: folder) } - + func removeFlag(_ uid: UInt64, flags: MCOMessageFlag, folder: String = INBOX) { - changeFlag(uid: uid, flags: flags, kind: MCOIMAPStoreFlagsRequestKind.remove, folderName: folder) + changeFlag(uid: uid, flags: flags, kind: MCOIMAPStoreFlagsRequestKind.remove, folderName: folder) } private func changeFlag(uid: UInt64, flags: MCOMessageFlag, kind: MCOIMAPStoreFlagsRequestKind, folderName: String){ @@ -318,7 +310,6 @@ class MailHandler { } } // TODO WHAT IF Folder does not exists? - } func loadMailsForInbox(completionCallback: @escaping ((_ error: MailServerConnectionError?) -> ())) { @@ -395,7 +386,7 @@ class MailHandler { } } } - + private func loadMessagesFromServer(_ uids: MCOIndexSet, folderPath: String, maxLoad: Int = MailHandler.MAXMAILS, completionCallback: @escaping ((_ error: MailServerConnectionError?) -> ())) { guard IMAPSession != nil else { completionCallback(MailServerConnectionError.NoData) @@ -455,78 +446,78 @@ class MailHandler { } } -/* TODO - func move(mails: [MailRecord], from: String, to: String, folderCreated: Bool = false) { - guard IMAPSession != nil else { - return - } - let uids = MCOIndexSet() - if !DataHandler.handler.existsFolder(with: to) && !folderCreated { - let op = IMAPSession?.createFolderOperation(to) - op?.start({[unowned self] error in - guard error == nil else { - let conError = MailServerConnectionError.findErrorCode(error: error!) - self.errorhandling(error: conError, originalCall: {self.move(mails: mails, from: from, to: to)}, completionCallback: nil) - return - } - self.move(mails: mails, from: from, to: to, folderCreated: true) - }) - } else { - let folderstatusFrom = IMAPSession?.folderStatusOperation(from) - folderstatusFrom?.start {[unowned self] (error, status) -> Void in - guard error == nil else { - let conerror = MailServerConnectionError.findErrorCode(error: error!) - self.errorhandling(error: conerror, originalCall: {self.move(mails: mails, from: from, to: to)}, completionCallback: nil) - return - } - if let statusFrom = status { - let uidValidity = statusFrom.uidValidity - let f = DataHandler.handler.findFolder(with: from) - if uidValidity == f.uidvalidity { - for mail in mails { - if mail.uidvalidity == uidValidity { - uids.add(mail.uid) - mail.folder.removeFromMails(mail) - if let record = mail.record { - record.removeFromPersistentMails(mail) - if record.mailsInFolder(folder: f).count == 0 { - f.removeFromKeyRecords(record) - } - } - DataHandler.handler.delete(mail: mail) - } - } - let op = self.IMAPSession?.moveMessagesOperation(withFolder: from, uids: uids, destFolder: to) - op?.start {[unowned self] - (err, vanished) -> Void in - guard err == nil else { - let conerror = MailServerConnectionError.findErrorCode(error: err!) - self.errorhandling(error: conerror, originalCall: {self.move(mails: mails, from: from, to: to)}, completionCallback: { err in - guard err != nil else { - return - } - let op = self.IMAPSession?.copyMessagesOperation(withFolder: from, uids: uids, destFolder: to) - op?.start({[unowned self] error, _ in - guard error == nil else { - return - } - uids.enumerate({uid in - self.setFlag(uid, flags: MCOMessageFlag.deleted, folder: from) - }) - }) - }) - return - } - } - } else { - f.uidvalidity = uidValidity - } - } - } - } - } - -*/ + /* TODO + func move(mails: [MailRecord], from: String, to: String, folderCreated: Bool = false) { + guard IMAPSession != nil else { + return + } + let uids = MCOIndexSet() + if !DataHandler.handler.existsFolder(with: to) && !folderCreated { + let op = IMAPSession?.createFolderOperation(to) + op?.start({[unowned self] error in + guard error == nil else { + let conError = MailServerConnectionError.findErrorCode(error: error!) + self.errorhandling(error: conError, originalCall: {self.move(mails: mails, from: from, to: to)}, completionCallback: nil) + return + } + self.move(mails: mails, from: from, to: to, folderCreated: true) + }) + } else { + let folderstatusFrom = IMAPSession?.folderStatusOperation(from) + folderstatusFrom?.start {[unowned self] (error, status) -> Void in + guard error == nil else { + let conerror = MailServerConnectionError.findErrorCode(error: error!) + self.errorhandling(error: conerror, originalCall: {self.move(mails: mails, from: from, to: to)}, completionCallback: nil) + return + } + if let statusFrom = status { + let uidValidity = statusFrom.uidValidity + let f = DataHandler.handler.findFolder(with: from) + if uidValidity == f.uidvalidity { + for mail in mails { + if mail.uidvalidity == uidValidity { + uids.add(mail.uid) + mail.folder.removeFromMails(mail) + if let record = mail.record { + record.removeFromPersistentMails(mail) + if record.mailsInFolder(folder: f).count == 0 { + f.removeFromKeyRecords(record) + } + } + DataHandler.handler.delete(mail: mail) + } + } + let op = self.IMAPSession?.moveMessagesOperation(withFolder: from, uids: uids, destFolder: to) + op?.start {[unowned self] + (err, vanished) -> Void in + guard err == nil else { + let conerror = MailServerConnectionError.findErrorCode(error: err!) + self.errorhandling(error: conerror, originalCall: {self.move(mails: mails, from: from, to: to)}, completionCallback: { err in + guard err != nil else { + return + } + let op = self.IMAPSession?.copyMessagesOperation(withFolder: from, uids: uids, destFolder: to) + op?.start({[unowned self] error, _ in + guard error == nil else { + return + } + uids.enumerate({uid in + self.setFlag(uid, flags: MCOMessageFlag.deleted, folder: from) + }) + }) + }) + return + } + } + } else { + f.uidvalidity = uidValidity + } + } + } + } + } + + */ func allFolders(_ completion: @escaping (Error?) -> Void) { guard IMAPSession != nil else { completion(MailServerConnectionError.NoData) @@ -546,17 +537,15 @@ class MailHandler { properities.append(property) } self.dataProvider.importNewData(from: properities, completionHandler: completion) - } }) } - - + private func initFolder(folderPath: String, completionCallback: @escaping ((MailServerConnectionError?) -> ())) { let requestKind = MCOIMAPMessagesRequestKind(rawValue: MCOIMAPMessagesRequestKind.headers.rawValue) let uids = MCOIndexSet(range: MCORangeMake(1, UINT64_MAX)) let toFetchIDs = MCOIndexSet() - + let fetchOperation: MCOIMAPFetchMessagesOperation = self.IMAPSession!.fetchMessagesOperation(withFolder: folderPath, requestKind: requestKind, uids: uids) fetchOperation.start {[unowned self] (err, msg, vanished) -> Void in guard err == nil else { @@ -575,12 +564,11 @@ class MailHandler { } } } - + private func initInbox(completionCallback: @escaping ((MailServerConnectionError?) -> ())) { initFolder(folderPath: MailHandler.INBOX, completionCallback: completionCallback) } - func updateFolder(folderpath: String, completionCallback: @escaping ((MailServerConnectionError?) -> ())) { guard IMAPSession != nil else { completionCallback(MailServerConnectionError.NoData) @@ -647,9 +635,7 @@ class MailHandler { requestIds.add(range) folder.minUID = NSDecimalNumber(value: start) } - } - return requestIds } @@ -658,7 +644,7 @@ class MailHandler { completionCallback(MailServerConnectionError.NoData) return } - + if let path = folder.path, let statusOP = session.folderStatusOperation(path) { statusOP.start{[unowned self](error, status) -> Void in guard error == nil else { @@ -673,7 +659,6 @@ class MailHandler { } } } - } private func olderMails(folder: FolderRecord, completionCallback: @escaping ((MailServerConnectionError?) -> ())) { @@ -695,7 +680,7 @@ class MailHandler { let folderPath = folder.path let searchExp = MCOIMAPSearchExpression.search(since: since) let searchOperation = self.IMAPSession?.searchExpressionOperation(withFolder: folderPath, expression: searchExp) - + searchOperation?.start{[unowned self] (err, uids) -> Void in guard err == nil else { let conerror = MailServerConnectionError.findErrorCode(error: err!) @@ -713,9 +698,9 @@ class MailHandler { completionCallback(nil) } } - + } - + func retryWithRefreshedOAuth(completion: @escaping () -> ()) { guard shouldTryRefreshOAUTH else { return @@ -740,14 +725,14 @@ class MailHandler { completionCallback!(error) } } -} - -// Helper function inserted by Swift 4.2 migrator. -fileprivate func convertToUIBackgroundTaskIdentifier(_ input: Int) -> UIBackgroundTaskIdentifier { - return UIBackgroundTaskIdentifier(rawValue: input) -} - -// Helper function inserted by Swift 4.2 migrator. -fileprivate func convertFromUIBackgroundTaskIdentifier(_ input: UIBackgroundTaskIdentifier) -> Int { - return input.rawValue -} + } + + // Helper function inserted by Swift 4.2 migrator. + fileprivate func convertToUIBackgroundTaskIdentifier(_ input: Int) -> UIBackgroundTaskIdentifier { + return UIBackgroundTaskIdentifier(rawValue: input) + } + + // Helper function inserted by Swift 4.2 migrator. + fileprivate func convertFromUIBackgroundTaskIdentifier(_ input: UIBackgroundTaskIdentifier) -> Int { + return input.rawValue + } diff --git a/enzevalos_iphone/New Group/Mailbot.swift b/enzevalos_iphone/New Group/Mailbot.swift index ad5271519e0a52cd43ef5a73b931d052ed088c09..7ed2e4effdd5ef3dc8c5cd3bb637af5776927a64 100644 --- a/enzevalos_iphone/New Group/Mailbot.swift +++ b/enzevalos_iphone/New Group/Mailbot.swift @@ -22,7 +22,7 @@ class Mailbot { static let welcomeBodyDE = - """ + """ Liebe Nutzerin, lieber Nutzer von Letterbox, Ich bin Lette, der Letterbox-Mailbot. Wir freuen uns, dass du dich für unsere App entschieden hast. Magst du mir gleich auf die E-Mail antworten? Dann kannst du gleich erfahren, wie einfach es ist sichere Mails zu schreiben. Du und ich wissen dann das es bei dir auch klappt. @@ -37,7 +37,7 @@ class Mailbot { static let welcomeBodyEn = - """ + """ Hi, Thank you for using and testing our app! This is an automatically generated email and you can reply and test how to write a confidential mail. Our bot will reply and you can get an idea about confidential communication. We look forward to receiving your feedback! @@ -77,11 +77,45 @@ class Mailbot { } let user = AddressProperties(email: email) var pkproperty: PublicKeyProperties? = nil - if let addrRecord = PersitentDataProvider.dataProvider.generateFetchedAddresesWithKeyResultsController(addresses: [senderAdr]).fetchedObjects?.first, let key = addrRecord.primaryKey { - pkproperty = PublicKeyProperties(fingerprint: key.keyID, cryptoProtocol: key.type, origin: Origin(rawValue: key.origin) ?? Origin.FileTransfer, preferEncryption: AutocryptState.find(i: Int(key.preferEncryption)), lastSeenInAutocryptHeader: key.lastSeenInAutocryptHeader, lastSeenSignedMail: key.lastSeenSignedMail, secretKeyProperty: nil, usedAddresses: [sender]) + if let addrRecord = PersistentDataProvider.dataProvider.generateFetchedAddresesWithKeyResultsController( + addresses: [senderAdr]).fetchedObjects?.first, + let key = addrRecord.primaryKey { + pkproperty = PublicKeyProperties( + fingerprint: key.keyID, + cryptoProtocol: key.type, + origin: Origin(rawValue: key.origin) ?? Origin.FileTransfer, + preferEncryption: AutocryptState.find(i: Int(key.preferEncryption)), + lastSeenInAutocryptHeader: key.lastSeenInAutocryptHeader, + lastSeenSignedMail: key.lastSeenSignedMail, + secretKeyProperty: nil, + usedAddresses: [sender]) } - - let mail = MailProperties(messageID: UUID().uuidString, subject: subject, date: Date(), flags: 0, from: sender, to: [user], cc: [], bcc: [], folder: FolderProperties(delimiter: nil, uidValidity: nil, lastUpdate: nil, maxUID: nil, minUID: nil, path: UserManager.backendInboxFolderPath, parent: nil, children: nil), body: body, attachments: [], signatureState: SignatureState.ValidSignature.rawValue, encryptionState: EncryptionState.ValidedEncryptedWithCurrentKey.rawValue, signatureKey: pkproperty, decryptionKey: nil, autocryptHeaderKey: [], attachedPublicKeys: [], attachedSecretKeys: []) - PersitentDataProvider.dataProvider.importNewData(from: [mail], completionHandler: {error in }) + + let mail = MailProperties( + messageID: UUID().uuidString, + subject: subject, date: Date(), + flags: 0, + from: sender, + to: [user], + cc: [], + bcc: [], + folder: FolderProperties( + delimiter: nil, + uidValidity: nil, + lastUpdate: nil, + maxUID: nil, + minUID: nil, + path: UserManager.backendInboxFolderPath, + parent: nil, children: nil), + body: body, + attachments: [], + signatureState: SignatureState.ValidSignature.rawValue, + encryptionState: EncryptionState.ValidedEncryptedWithCurrentKey.rawValue, + signatureKey: pkproperty, + decryptionKey: nil, + autocryptHeaderKey: [], + attachedPublicKeys: [], + attachedSecretKeys: []) + PersistentDataProvider.dataProvider.importNewData(from: [mail], completionHandler: {error in }) } } diff --git a/enzevalos_iphone/OutgoingMail.swift b/enzevalos_iphone/OutgoingMail.swift index 580ee7902447c98e5959de8b60211c6f39c2e49d..5227d034aed580274f7d0590ba5d6681bdec5d32 100644 --- a/enzevalos_iphone/OutgoingMail.swift +++ b/enzevalos_iphone/OutgoingMail.swift @@ -7,41 +7,43 @@ // import Foundation + class OutgoingMail { static var OutgoingFolder = FolderProperties(delimiter: nil, uidValidity: nil, lastUpdate: nil, maxUID: nil, minUID: nil, path: "OutgoingMails", parent: nil, children: nil) - private var encAddresses: [MCOAddress] = [] var encReceivers: [MCOAddress] { get { return encAddresses } } + private var plainAddresses: [MCOAddress] = [] var plainReceivers: [MCOAddress] { get { return plainAddresses } } - private let toEntrys: [MCOAddress] - private let ccEntrys: [MCOAddress] - private let bccEntrys: [MCOAddress] + + private let toAddresses: [MCOAddress] + private let ccAddresses: [MCOAddress] + private let bccAddresses: [MCOAddress] var username = UserManager.loadUserValue(Attribute.accountname) as? String ?? "" - var useraddr = (UserManager.loadUserValue(Attribute.userAddr) as? String ?? "") + var useraddress = (UserManager.loadUserValue(Attribute.userAddr) as? String ?? "") var userDisplayName = UserManager.loadUserValue(Attribute.userDisplayName) as? String var sender: MCOAddress { get { - if let name = userDisplayName, name.lowercased() != useraddr { - return MCOAddress.init(displayName: name, mailbox: useraddr) + if let name = userDisplayName, name.lowercased() != useraddress { + return MCOAddress.init(displayName: name, mailbox: useraddress) } else { - return MCOAddress.init(displayName: useraddr, mailbox: useraddr) + return MCOAddress.init(displayName: useraddress, mailbox: useraddress) } } } private var secretKeyID: String { get { - return PersitentDataProvider.prefKeyID + return PersistentDataProvider.prefKeyID } } private let subject: String @@ -108,10 +110,18 @@ class OutgoingMail { private var mail: MailRecord? - init(toEntrys: [String], ccEntrys: [String], bccEntrys: [String], subject: String, textContent: String, htmlContent: String?, textparts: Int = 0, sendEncryptedIfPossible: Bool = true, attachments: [MCOAttachment] = []) { - self.toEntrys = OutgoingMail.mapToMCOAddresses(addr: toEntrys) - self.ccEntrys = OutgoingMail.mapToMCOAddresses(addr: ccEntrys) - self.bccEntrys = OutgoingMail.mapToMCOAddresses(addr: bccEntrys) + init(toAddresses: [String], + ccAddresses: [String], + bccAddresses: [String], + subject: String, + textContent: String, + htmlContent: String?, + textparts: Int = 0, + sendEncryptedIfPossible: Bool = true, + attachments: [MCOAttachment] = []) { + self.toAddresses = OutgoingMail.mapToMCOAddresses(addr: toAddresses) + self.ccAddresses = OutgoingMail.mapToMCOAddresses(addr: ccAddresses) + self.bccAddresses = OutgoingMail.mapToMCOAddresses(addr: bccAddresses) self.subject = subject self.textContent = textContent self.htmlContent = htmlContent @@ -125,13 +135,12 @@ class OutgoingMail { self.attachedSignature = signatureData } } - } init(mail: MailRecord){ - toEntrys = OutgoingMail.mapToMCOAddresses(addr: mail.tos) - ccEntrys = OutgoingMail.mapToMCOAddresses(addr: mail.ccs) - bccEntrys = OutgoingMail.mapToMCOAddresses(addr: mail.bccs) + toAddresses = OutgoingMail.mapToMCOAddresses(addr: mail.tos) + ccAddresses = OutgoingMail.mapToMCOAddresses(addr: mail.ccs) + bccAddresses = OutgoingMail.mapToMCOAddresses(addr: mail.bccs) subject = mail.subject textContent = mail.body htmlContent = "" //.decryptedBody //TODO FIX HERE @@ -147,10 +156,10 @@ class OutgoingMail { func store() { // Problem: Was ist die MessageID? let body = "" + (htmlContent ?? "") + (textContent ?? "") - let bcc = bccEntrys.map({AddressProperties(email: $0.mailbox, name: $0.displayName)}) - let cc = ccEntrys.map({AddressProperties(email: $0.mailbox, name: $0.displayName)}) + let bcc = bccAddresses.map({AddressProperties(email: $0.mailbox, name: $0.displayName)}) + let cc = ccAddresses.map({AddressProperties(email: $0.mailbox, name: $0.displayName)}) let attachmentProperties = self.attachments.map({AttachmentProperties(type: 0, name: $0.filename ?? "", mimeType: $0.mimeType, contentID: $0.contentID, data: $0.data)}) - let to = self.toEntrys.map({AddressProperties(email: $0.mailbox, name: $0.displayName)}) + let to = self.toAddresses.map({AddressProperties(email: $0.mailbox, name: $0.displayName)}) let sigState = self.cryptoObject?.signatureState.rawValue ?? SignatureState.NoSignature.rawValue let encState = self.cryptoObject?.encryptionState.rawValue ?? EncryptionState.NoEncryption.rawValue @@ -380,7 +389,7 @@ class OutgoingMail { pgpKeyIds.append(contentsOf: OutgoingMail.addKeys(adrs: encAddresses, cryptoScheme: .PGP)) } // We add our public key if one uses pgp but may not have our public key - if let keys = PersitentDataProvider.dataProvider.generateFetchedPublicKeysResultController(keyIds: pgpKeyIds)?.fetchedObjects { + if let keys = PersistentDataProvider.dataProvider.generateFetchedPublicKeysResultController(keyIds: pgpKeyIds)?.fetchedObjects { for key in keys { if !key.receivedMyPublicKey { missingOwnPublic = true @@ -404,35 +413,34 @@ class OutgoingMail { private func orderReceivers(){ if !self.sendEncryptedIfPossible { - plainAddresses.append(contentsOf: toEntrys) - plainAddresses.append(contentsOf: ccEntrys) - plainAddresses.append(contentsOf: bccEntrys) + plainAddresses.append(contentsOf: toAddresses) + plainAddresses.append(contentsOf: ccAddresses) + plainAddresses.append(contentsOf: bccAddresses) return } var addresses = [String]() - addresses.append(contentsOf: toEntrys.map({$0.mailbox})) - addresses.append(contentsOf: ccEntrys.map({$0.mailbox})) - addresses.append(contentsOf: bccEntrys.map({$0.mailbox})) + addresses.append(contentsOf: toAddresses.map({$0.mailbox})) + addresses.append(contentsOf: ccAddresses.map({$0.mailbox})) + addresses.append(contentsOf: bccAddresses.map({$0.mailbox})) if let keys = LetterboxModel.instance.dataProvider.generateFetchedAddresesWithKeyResultsController(addresses: addresses).fetchedObjects { let addrsWithKeys = keys.map({$0.email}) - encAddresses.append(contentsOf: toEntrys.filter({ addrsWithKeys.contains($0.mailbox) })) - encAddresses.append(contentsOf: ccEntrys.filter({ addrsWithKeys.contains($0.mailbox) })) - encAddresses.append(contentsOf: bccEntrys.filter({ addrsWithKeys.contains($0.mailbox) })) - plainAddresses.append(contentsOf: toEntrys.filter({ !addrsWithKeys.contains($0.mailbox) })) - plainAddresses.append(contentsOf: ccEntrys.filter({ !addrsWithKeys.contains($0.mailbox) })) - plainAddresses.append(contentsOf: bccEntrys.filter({ !addrsWithKeys.contains($0.mailbox) })) - + encAddresses.append(contentsOf: toAddresses.filter({ addrsWithKeys.contains($0.mailbox) })) + encAddresses.append(contentsOf: ccAddresses.filter({ addrsWithKeys.contains($0.mailbox) })) + encAddresses.append(contentsOf: bccAddresses.filter({ addrsWithKeys.contains($0.mailbox) })) + plainAddresses.append(contentsOf: toAddresses.filter({ !addrsWithKeys.contains($0.mailbox) })) + plainAddresses.append(contentsOf: ccAddresses.filter({ !addrsWithKeys.contains($0.mailbox) })) + plainAddresses.append(contentsOf: bccAddresses.filter({ !addrsWithKeys.contains($0.mailbox) })) } } private func createBuilder() -> MCOMessageBuilder { let builder = MCOMessageBuilder() - builder.header.to = toEntrys - builder.header.cc = ccEntrys - builder.header.bcc = bccEntrys + builder.header.to = toAddresses + builder.header.cc = ccAddresses + builder.header.bcc = bccAddresses builder.header.from = sender builder.header.subject = subject // builder.header.setExtraHeaderValue("1.0", forName: "MIME-Version") @@ -473,7 +481,7 @@ class OutgoingMail { private static func addKeys(adrs: [MCOAddress], cryptoScheme: CryptoScheme) -> [String] { var ids = [String]() - if let records = PersitentDataProvider.dataProvider.generateFetchedAddresesWithKeyResultsController(addresses: adrs.map({$0.mailbox})).fetchedObjects { + if let records = PersistentDataProvider.dataProvider.generateFetchedAddresesWithKeyResultsController(addresses: adrs.map({$0.mailbox})).fetchedObjects { for adr in records { if let prim = adr.primaryKey, prim.type == cryptoScheme, let id = prim.fingerprint { ids.append(id) @@ -483,20 +491,20 @@ class OutgoingMail { return ids } - static func createDraft (toEntrys: [String], ccEntrys: [String], bccEntrys: [String], subject: String, textContent: String, htmlContent: String?) -> OutgoingMail{ - let mail = OutgoingMail(toEntrys: toEntrys, ccEntrys: ccEntrys, bccEntrys: bccEntrys, subject: subject, textContent: textContent, htmlContent: htmlContent) + static func createDraft(toAddresses: [String], ccAddresses: [String], bccAddresses: [String], subject: String, textContent: String, htmlContent: String?) -> OutgoingMail{ + let mail = OutgoingMail(toAddresses: toAddresses, ccAddresses: ccAddresses, bccAddresses: bccAddresses, subject: subject, textContent: textContent, htmlContent: htmlContent) mail.isDraft = true return mail } - static func createInvitationMail(toEntrys: [String], ccEntrys: [String], bccEntrys: [String], subject: String, textContent: String, htmlContent: String?) -> OutgoingMail{ - let mail = OutgoingMail(toEntrys: toEntrys, ccEntrys: ccEntrys, bccEntrys: bccEntrys, subject: subject, textContent: textContent, htmlContent: htmlContent) + static func createInvitationMail(toAddresses: [String], ccAddresses: [String], bccAddresses: [String], subject: String, textContent: String, htmlContent: String?) -> OutgoingMail{ + let mail = OutgoingMail(toAddresses: toAddresses, ccAddresses: ccAddresses, bccAddresses: bccAddresses, subject: subject, textContent: textContent, htmlContent: htmlContent) mail.inviteMail = true return mail } static func createSecretKeyExportMail(keyID: String, keyData: String) -> OutgoingMail{ let useraddr = (UserManager.loadUserValue(Attribute.userAddr) as! String) - let mail = OutgoingMail(toEntrys: [useraddr], ccEntrys: [], bccEntrys: [], subject: "Autocrypt Setup Message", textContent: "", htmlContent: nil) + let mail = OutgoingMail(toAddresses: [useraddr], ccAddresses: [], bccAddresses: [], subject: "Autocrypt Setup Message", textContent: "", htmlContent: nil) mail.plainAddresses = mail.encAddresses mail.encAddresses = [] mail.exportSecretKey = true @@ -505,7 +513,7 @@ class OutgoingMail { return mail } static func createLoggingMail(addr: String, textcontent: String) -> OutgoingMail{ - let mail = OutgoingMail(toEntrys: [addr], ccEntrys: [], bccEntrys: [], subject: "[Letterbox] Log", textContent: textcontent, htmlContent: nil) + let mail = OutgoingMail(toAddresses: [addr], ccAddresses: [], bccAddresses: [], subject: "[Letterbox] Log", textContent: textcontent, htmlContent: nil) mail.loggingMail = true return mail } diff --git a/enzevalos_iphone/PGP/Autocrypt.swift b/enzevalos_iphone/PGP/Autocrypt.swift index 913ed6cf38eb89cc0bf11c0006c36d3a360cb6a8..a1c1b2f38b38e6e2875e90f766a3302721f261bd 100644 --- a/enzevalos_iphone/PGP/Autocrypt.swift +++ b/enzevalos_iphone/PGP/Autocrypt.swift @@ -179,7 +179,7 @@ class Autocrypt { let encPref = AutocryptState.MUTUAL let pgp = SwiftPGP() - if let key = pgp.exportKey(id: PersitentDataProvider.prefKeyID, isSecretkey: false, autocrypt: true) { + if let key = pgp.exportKey(id: PersistentDataProvider.prefKeyID, isSecretkey: false, autocrypt: true) { var string = "\(ADDR)=" + adr string = string + "; \(ENCRYPTION)=\(encPref.name)" string = string + "; \(KEY)=" + key @@ -241,5 +241,4 @@ class Autocrypt { } } - } diff --git a/enzevalos_iphone/PGP/SwiftPGP.swift b/enzevalos_iphone/PGP/SwiftPGP.swift index 79939ad2cf430bddb8c0e186c19d3fccbe0d2ec5..1a7ece176f1e3b3465659de64db06b3f9b839acb 100644 --- a/enzevalos_iphone/PGP/SwiftPGP.swift +++ b/enzevalos_iphone/PGP/SwiftPGP.swift @@ -511,7 +511,7 @@ class SwiftPGP: Encryption { var encState = EncryptionState.UnableToDecrypt var sigKeyID: String? = nil var signedAdr = [String]() - let prefID = PersitentDataProvider.prefKeyID + let prefID = PersistentDataProvider.prefKeyID var keyring = Keyring() var validDecryptionIDs: [String] = [] @@ -615,7 +615,7 @@ class SwiftPGP: Encryption { var encState = EncryptionState.UnableToDecrypt var plaindata: Data? = nil let key: [Key] = keyAsKeyList(keyID: keyID) - let controller = PersitentDataProvider.dataProvider.generateFetchedUnableToDecryptMailsResultsController() + let controller = PersistentDataProvider.dataProvider.generateFetchedUnableToDecryptMailsResultsController() if ((try? controller.performFetch()) != nil), let mailList = controller.fetchedObjects { @@ -631,7 +631,7 @@ class SwiftPGP: Encryption { mail.encryptionStateInt = encState.rawValue mail.body = String.init(data: plaindata, encoding: .utf8) ?? "" do { - try PersitentDataProvider.dataProvider.save(taskContext: controller.managedObjectContext) + try PersistentDataProvider.dataProvider.save(taskContext: controller.managedObjectContext) } catch { print("Could not store decrypted mails!") } @@ -672,7 +672,7 @@ class SwiftPGP: Encryption { func findNotSignedMailForPublicKey(keyID: String) { var sigState = SignatureState.NoPublicKey let key: [Key] = keyAsKeyList(keyID: keyID) - let datahandler = PersitentDataProvider.dataProvider + let datahandler = PersistentDataProvider.dataProvider if let mailList = datahandler.fetchedMailResultsController.fetchedObjects { //TODO look for not verified signed mails. for mail in mailList { // Change data diff --git a/enzevalos_iphone/PreviewSampleData/SimulatorData.swift b/enzevalos_iphone/PreviewSampleData/SimulatorData.swift index 5040ecc758a501989153c8bec7803bf8a34fc808..88cd8056f6caf617fa48c4a053884bc28d1966d9 100644 --- a/enzevalos_iphone/PreviewSampleData/SimulatorData.swift +++ b/enzevalos_iphone/PreviewSampleData/SimulatorData.swift @@ -37,9 +37,9 @@ struct SimulatorData_Previews: PreviewProvider { struct ProxyData { // Different Contacts - static let Alice = ProxyContact(name: "Alice", email: "alice@example.com", myImage: ProxyContact.makeImg("Alice", color: .blue)) - static let Bob = ProxyContact(name: "Bob", email: "Bob.lord.of.kingsbridge.and.king.of.england@huge.subdomain.with. a.long.long.long.domain.example.com", myImage: ProxyContact.makeImg("Bob", color: .red)) - static let Charlie = ProxyContact(name: "Charlie", email: "charlie@example.com", myImage: ProxyContact.makeImg("Charlie", color: .green)) + static let Alice = ProxyContact(name: "Alice", email: "alice@example.com", avatar: ProxyContact.makeImg("Alice", color: .blue)) + static let Bob = ProxyContact(name: "Bob", email: "Bob.lord.of.kingsbridge.and.king.of.england@huge.subdomain.with. a.long.long.long.domain.example.com", avatar: ProxyContact.makeImg("Bob", color: .red)) + static let Charlie = ProxyContact(name: "Charlie", email: "charlie@example.com", avatar: ProxyContact.makeImg("Charlie", color: .green)) static let Landmarks = [ Landmark(name: "Berlin", domain: "exampledomain.de", location: .init(latitude: 52.520008, longitude: 13.404954)), Landmark(name: "New York", domain: "secondexampledomain.de", location: .init(latitude: 40.730610, longitude: -73.935242)), @@ -101,7 +101,7 @@ struct ProxyContact: DisplayContact { var email: String - var myImage: Image + var avatar: Image var isInContactBook: Bool = false diff --git a/enzevalos_iphone/SwiftUI/Compose/AddressTokenView.swift b/enzevalos_iphone/SwiftUI/Compose/AddressTokenView.swift deleted file mode 100644 index 7779e0147be9f3f04141bcadc20913a78ebda2b8..0000000000000000000000000000000000000000 --- a/enzevalos_iphone/SwiftUI/Compose/AddressTokenView.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// AddressTokenView.swift -// enzevalos_iphone -// -// Created by Oliver Wiese on 31.10.20. -// Copyright © 2020 fu-berlin. All rights reserved. -// - -import SwiftUI - -struct AddressTokenView <C: DisplayContact>: View { - var contact: C - - @State var extended = true - @State var edit = false - - - var body: some View { - if edit && !extended { - // TODO -> New Text edit field or remove item? - Text("EDIT") - } - else { - HStack{ - CircleImage(image: contact.myImage, radius: 25) - Text(contact.name) - if extended { - extendedView - } - } - .fixedSize(horizontal: true, vertical: true) - } - } - - var extendedView: some View { - HStack { - if contact.email != contact.name { - Text("<\(contact.email)>") - .font(.footnote) - .foregroundColor(.gray) - } - if contact.primaryKey != nil { - Image(systemName: "key") - .transformEffect(.init(rotationAngle: -90)) - .offset(x: 0, y: 20) - .foregroundColor(.green) - } - } - } -} - -struct AddressTokenView_Previews: PreviewProvider { - static var previews: some View { - AddressTokenView<ProxyContact>(contact: ProxyData.Alice) - AddressTokenView<ProxyContact>(contact: ProxyData.Bob) - - } -} diff --git a/enzevalos_iphone/SwiftUI/Compose/BodyView.swift b/enzevalos_iphone/SwiftUI/Compose/BodyView.swift deleted file mode 100644 index 75ac8b68eb2f6b6d5013b96ebbb3578b98626aed..0000000000000000000000000000000000000000 --- a/enzevalos_iphone/SwiftUI/Compose/BodyView.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// BodyView.swift -// enzevalos_iphone -// -// Created by Oliver Wiese on 11.11.20. -// Copyright © 2020 fu-berlin. All rights reserved. -// - -import SwiftUI - -struct BodyView: View { - @ObservedObject var model: ComposeModel - - var body: some View { - TextEditor(text: $model.body) - } - - var subject: some View { - HStack { - Text("Subject") - Text(":") - TextField("", text: $model.subject) - } - } -} - -struct BodyView_Previews: PreviewProvider { - static var previews: some View { - BodyView(model: ComposeModel(preData: nil)) - } -} diff --git a/enzevalos_iphone/SwiftUI/Compose/ComposeModel.swift b/enzevalos_iphone/SwiftUI/Compose/ComposeModel.swift index 47a890c79161924652a01933d41bcff0ff69c6d8..7d3dae27203df71f874f8d101bb9b18583442cb9 100644 --- a/enzevalos_iphone/SwiftUI/Compose/ComposeModel.swift +++ b/enzevalos_iphone/SwiftUI/Compose/ComposeModel.swift @@ -8,36 +8,50 @@ import Foundation - +/// A model used to compose and send an email. class ComposeModel: ObservableObject { @Published var subject = "" @Published var body = "" - @Published var deactivateEncryption = false + @Published var encryptionOff = false + var recipientsModel: RecipientsModel = RecipientsModel() init(preData: PreMailData?) { if let preData = preData { subject = preData.subject body = preData.body - addAddresses(addresses: preData.to, model: receiverModel.toModel) - addAddresses(addresses: preData.cc, model: receiverModel.ccModel) - addAddresses(addresses: preData.bcc, model: receiverModel.bccModel) - } - } - - private func addAddresses(addresses: [String], model: TokenFieldModel) { - for addr in addresses { - model.addNewAddr(new: addr) + addAddresses(preData.to, model: recipientsModel.toModel) + addAddresses(preData.cc, model: recipientsModel.ccModel) + addAddresses(preData.bcc, model: recipientsModel.bccModel) } } - var receiverModel: SelectReceiverModel = SelectReceiverModel() - // TODO Add security state + // TODO: Add security state - func sentMail() { + /// Generates mail and sends it. + func sendMail() { generateMail().send() } + /// Adds email addresses to given RecipientFieldModel. + /// + /// - Parameter addresses: String array of email addresses to add. + /// - Parameter model: RecipientFieldModel to which to add the addresses. + private func addAddresses(_ addresses: [String], model: RecipientFieldModel) { + for address in addresses { + model.addNewAddress(address) + } + } + + /// Generates OutgoingMail with given email contents. private func generateMail() -> OutgoingMail { - return OutgoingMail(toEntrys: receiverModel.toEMails, ccEntrys: receiverModel.ccEMails, bccEntrys: receiverModel.bccEMails, subject: subject, textContent: body, htmlContent: nil, textparts: 1, sendEncryptedIfPossible: !deactivateEncryption, attachments: []) + OutgoingMail(toAddresses: recipientsModel.toEMails, + ccAddresses: recipientsModel.ccEMails, + bccAddresses: recipientsModel.bccEMails, + subject: subject, + textContent: body, + htmlContent: nil, + textparts: 1, + sendEncryptedIfPossible: !encryptionOff, + attachments: []) } } diff --git a/enzevalos_iphone/SwiftUI/Compose/ComposeView.swift b/enzevalos_iphone/SwiftUI/Compose/ComposeView.swift index 27853078bf08ff01115e5c83f87ec5d634628cff..9592800013bf53993b6eea605ac30c73b111c334 100644 --- a/enzevalos_iphone/SwiftUI/Compose/ComposeView.swift +++ b/enzevalos_iphone/SwiftUI/Compose/ComposeView.swift @@ -1,98 +1,196 @@ // // ComposeView.swift -// enzevalos_iphone +// letterbox_prototyping // -// Created by Oliver Wiese on 11.11.20. -// Copyright © 2020 fu-berlin. All rights reserved. +// Created by Chris Offner & Claire Bräuer on 03.03.21. // import SwiftUI +/// A view used to compose and send an email. struct ComposeView: View { - var maxViewIndex = 2 - var minViewIndex = 1 - - @State private var currentView = 2 - @ObservedObject var model: ComposeModel - @Environment(\.presentationMode) var presentationMode + @State private var cc = "" + @State private var isEditingCcOrBcc = false + @ObservedObject var composer: ComposeModel + /// - Parameter preData: Data of email to reply to or forward. init(preData: PreMailData? = nil) { - model = ComposeModel(preData: preData) + composer = ComposeModel(preData: preData) } var body: some View { - multipleViews - } - - private var singleView: some View { - ScrollView { - TokenField(viewModel: model.receiverModel.toModel) + VStack { + // Top bar with Cancel and Send button + ComposeViewHeader() + .environmentObject(composer) + Divider() - TokenField(viewModel: model.receiverModel.ccModel) + + // "To" recipient + RecipientField(recipientFieldModel: composer.recipientsModel.toModel) Divider() - TokenField(viewModel: model.receiverModel.bccModel) + + // "Cc/Bcc" recipient + if isEditingCcOrBcc || !cc.isEmpty { + RecipientField(recipientFieldModel: composer.recipientsModel.ccModel) + + Divider() + + RecipientField(recipientFieldModel: composer.recipientsModel.bccModel) + } else { + // TODO: Solve "Cc/Bcc" <--> "Cc", "Bcc" switch more elegantly. + // "Cc/Bcc" field never actually gets used, it gets replaced by + // separate "Cc" and "Bcc" RecipientFields above, once selected. + TextField("Cc" + "/" + "Bcc", text: $cc) { ccSelected in + self.isEditingCcOrBcc = ccSelected + } + } + Divider() + + // Subject HStack { Text("Subject") - Text(":") - TextField("", text: $model.subject) + .foregroundColor(Color(UIColor.tertiaryLabel)) + TextField("", text: $composer.subject) + .autocapitalization(.none) + .frame(minWidth: 0, maxWidth: .infinity) } Divider() - TextEditor(text: $model.body) + + // Email body + TextEditor(text: $composer.body) } .padding() - .navigationBarItems(trailing: Button("Send", action: { - model.sentMail() - presentationMode.wrappedValue.dismiss() - }) - ) + .animation(.default) + Spacer() } +} + +/// A view in which recipients get added or removed. +struct RecipientField: View { + @ObservedObject var recipientFieldModel: RecipientFieldModel + @State var showList = false - - private var multipleViews: some View { + var body: some View { VStack { - HStack{ - Button("Cancel", action: { - presentationMode.wrappedValue.dismiss() - }) - Picker("", selection: $currentView, content: { - //Text("Security").tag(0) // "shield" - Image(systemName: "person.fill").tag(1) - Image(systemName: "text.bubble.fill").tag(2) - }) - .pickerStyle(SegmentedPickerStyle()) - Button("Send", action: { - model.sentMail() - presentationMode.wrappedValue.dismiss() - }) + HStack { + // Recipient text field + Text(recipientFieldModel.type.asString) + .foregroundColor(Color(UIColor.tertiaryLabel)) + + // Shows selected recipients as blue capsules + // followed by TextField for new recipients + ScrollView(.horizontal) { + HStack(spacing: 4) { + ForEach(recipientFieldModel.selectedContacts) { (recipient: AddressRecord) in + Text(recipient.displayname ?? recipient.email) + .foregroundColor(.white) + .padding(.horizontal, 12) + .padding(.vertical, 2) + .background(Color.accentColor) + .clipShape(Capsule()) + } + TextField("", text: $recipientFieldModel.text, onCommit: { + recipientFieldModel.commitText() + }) + .frame(minWidth: 200) + .autocapitalization(.none) + .keyboardType(.emailAddress) + } + } + + // Toggles contact list + Button(action: { showList.toggle() }) { + Image(systemName: !showList ? "plus.circle" : "chevron.up.circle") + } } + .frame(height: 20) - Divider() - .padding(.leading) - if currentView == 0 { - Text("SECURITY") - } else if currentView == 1 { - SelectReceiverView(model: model) - } else { - BodyView(model: model) + // Contact list + if showList { + Divider() + RecipientListView() + .frame(height: 464) } } - .navigationBarItems(trailing: Button("Send", action: { - model.sentMail() - presentationMode.wrappedValue.dismiss() - }) - ) - .navigationTitle(model.subject) - .padding() - .gesture(DragGesture() - .onEnded { - if $0.translation.width < -100 { - self.currentView = max(minViewIndex, self.currentView - 1) - } else if $0.translation.width > 100 { - self.currentView = min(maxViewIndex, self.currentView + 1) + .environmentObject(recipientFieldModel) + } +} + +/// A view containing the Cancel and Send buttons for an email. +struct ComposeViewHeader: View { + @Environment(\.presentationMode) var presentationMode + @EnvironmentObject var composer: ComposeModel + + var body: some View { + VStack { + // Grab handle in top center + Capsule() + .fill(Color(.lightGray)) + .frame(width: 70, height: 5) + + + HStack { + // Cancel button + Button("Cancel") { + presentationMode.wrappedValue.dismiss() + } + + Spacer() + + // Send button + Button(action: { + composer.sendMail() + presentationMode.wrappedValue.dismiss() + }) { + if composer.encryptionOff { + UnencryptedSendButton(grayedOut: composer.recipientsModel.hasNoRecipients) + } else { + EncryptedSendButton(grayedOut: composer.recipientsModel.hasNoRecipients) + } } + .disabled(composer.recipientsModel.hasNoRecipients) } - ) + } + } +} + +/// Styling for Send button if email gets sent encrypted. +struct EncryptedSendButton: View { + var grayedOut: Bool + + var body: some View { + HStack { + Image(systemName: "lock") + .foregroundColor(Color.white) + Spacer() + Text("Send") + .foregroundColor(Color.white) + } + .padding(.horizontal) + .padding(.vertical, 5) + .background(Capsule().fill(grayedOut ? Color(UIColor.tertiaryLabel) : Color.blue)) + .frame(width: 101, height: 40) + } +} + +/// Styling for Send button if email gets sent unencrypted. +struct UnencryptedSendButton: View { + var grayedOut: Bool + + var body: some View { + HStack { + Image(systemName: "lock.open") + .foregroundColor(Color.blue) + Spacer() + Text("Send") + .foregroundColor(Color.blue) + } + .padding(.horizontal) + .padding(.vertical, 5) + .background(Capsule().stroke()) + .frame(width: 101, height: 40) } } diff --git a/enzevalos_iphone/SwiftUI/Compose/TokenFieldModel.swift b/enzevalos_iphone/SwiftUI/Compose/RecipientFieldModel.swift similarity index 62% rename from enzevalos_iphone/SwiftUI/Compose/TokenFieldModel.swift rename to enzevalos_iphone/SwiftUI/Compose/RecipientFieldModel.swift index 01dcee3a865058e5e19fee3d4b12c32fc83668a9..34cba3ee041c238f0355e6dea1a1607580f9766b 100644 --- a/enzevalos_iphone/SwiftUI/Compose/TokenFieldModel.swift +++ b/enzevalos_iphone/SwiftUI/Compose/RecipientFieldModel.swift @@ -9,28 +9,31 @@ import Combine import SwiftUI -class TokenFieldModel: ObservableObject { - private var dataprovider: PersitentDataProvider - let type: TokenFieldType - var selectReceiverModel: SelectReceiverModel? +/// A model for a single recipient field. +class RecipientFieldModel: ObservableObject { + @Published var suggestions: [AddressRecord] = [] + @Published var selectedContacts = [AddressRecord]() // TODO: As AddressProperty??? + private var dataprovider: PersistentDataProvider + let type: RecipientType - init(type: TokenFieldType, parent: SelectReceiverModel?, dataprovider: PersitentDataProvider) { + init(type: RecipientType, parent: RecipientsModel?, dataprovider: PersistentDataProvider) { self.type = type - self.selectReceiverModel = parent self.dataprovider = dataprovider } - @Published var text = "" { didSet { - if text.contains(" ") { // TODO add , ; return ... - let words = text.split(separator: "\n") - if let new = words.first { - addNewAddr(new: String(new)) + if text.contains(" ") || text.contains(",") || text.contains(";") { + var firstWord = text.split(separator: "\n").first + firstWord?.removeLast() + if let newWord = firstWord { + addNewAddress(String(newWord)) } text = "" } - if text != "" { + + // TODO: Show suggestions in ComposeView. + if !text.isEmpty { let frc = dataprovider.createFetchResultController(fetchRequest: AddressRecord.lookForPrefix(prefix: text)) if let addresses = frc.fetchedObjects { suggestions = addresses @@ -43,43 +46,41 @@ class TokenFieldModel: ObservableObject { } } - @Published var suggestions: [AddressRecord] = [] + /// Adds contact at index to recipients. + func selectContact(addr: AddressRecord) { + selectedContacts.append(addr) + text = "" + suggestions = [] + } - @Published var selectedContacts = [AddressRecord]() // TODO: As AddressProperty??? - + /// Removes contact at index from recipients. func unselectContactAt(i: Int) { if i < selectedContacts.count { selectedContacts.remove(at: i) - } else { print("ERROR wrong index! \(i) but \(selectedContacts.count)") } } - func selectContact(addr: AddressRecord) { - selectedContacts.append(addr) - text = "" - suggestions = [] - } - + /// Commits text to addresses. func commitText() { - if text != "" { - addNewAddr(new: text) + if !text.isEmpty { + addNewAddress(text) text = "" } } - func addNewAddr(new: String) { - guard new.count > 0 else { + func addNewAddress(_ address: String) { + guard address.count > 0 else { // TODO: Add email validiation + warning? return } - dataprovider.importNewData(from: [AddressProperties(email: new)], completionHandler: {error in - let frc = self.dataprovider.createFetchResultController(fetchRequest: AddressRecord.lookForExisting(email: new)) + + dataprovider.importNewData(from: [AddressProperties(email: address)]) { error in + 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 new file mode 100644 index 0000000000000000000000000000000000000000..db3c21a6e428ceb04ec87cd9a9a56ce1a0d1e399 --- /dev/null +++ b/enzevalos_iphone/SwiftUI/Compose/RecipientListView.swift @@ -0,0 +1,47 @@ +// +// RecipientListView.swift +// letterbox_prototyping +// +// Created by Chris Offner & Claire Bräuer on 11.03.21. +// + +import SwiftUI + +/// A view that lists contacts for selection as recipients. +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 + HStack { + Text("Sort by:") + .foregroundColor(.secondary) + Button(sortByName ? "Name" : "Last Contacted") { + sortByName.toggle() + } + } + .font(.caption) + .transition(.opacity) + .id(sortByName ? "Name" : "Last Contacted") + + // Contact list + ScrollView { + ForEach(contacts) { contact in + RecipientRowView(contact: contact) + Divider() + } + } + } + } +} diff --git a/enzevalos_iphone/SwiftUI/Compose/RecipientRowView.swift b/enzevalos_iphone/SwiftUI/Compose/RecipientRowView.swift new file mode 100644 index 0000000000000000000000000000000000000000..93b85acaaab43f1c3d09582ccec0dd480c4111cf --- /dev/null +++ b/enzevalos_iphone/SwiftUI/Compose/RecipientRowView.swift @@ -0,0 +1,62 @@ +// +// recipientRowView.swift +// letterbox_prototyping +// +// Created by Chris Offner & Claire Bräuer on 11.03.21. +// + +import SwiftUI + +/// A view that displays a single contact for recipient selection. +struct RecipientRowView: View { + let contact: AddressRecord + @EnvironmentObject var model: RecipientFieldModel + + var body: some View { + HStack { + // Profile picture + contact.avatar + .resizable() + .frame(width: 30, height: 30) + .aspectRatio(contentMode: .fit) + .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/) + .shadow(radius: 3) + .padding(.trailing, 8) + + VStack(alignment: .leading) { + HStack { + // TODO: Show proper displayname. + // Currently for debugging purposes we show the email + // before "@" as name because displaynames are all nil. + Text(contact.displayname ?? contact.email.components(separatedBy: "@")[0]) + Spacer() + Text(contact.lastHeardFrom) + .font(.caption) + .foregroundColor(.secondary) + .padding(.trailing, 16) + } + + // Currently only shows first email address of contact. + // TODO: Decide which email address(es) to show. + Text(contact.email) + .font(.caption) + .foregroundColor(.secondary) + } + + Spacer() + + // Adds or removes contact from recipients. + Button(action: { + if let index = model.selectedContacts.firstIndex(where: { $0.email == contact.email }) { + model.unselectContactAt(i: index) + } else { + model.selectContact(addr: contact) + } + }) { + // TODO: Maybe use more robust identifier (Identifiable + UUID?) + Image(systemName: model.selectedContacts.contains(where: { $0.email == contact.email }) ? "checkmark.circle.fill" : "circle") + .foregroundColor(.accentColor) + } + } + } +} diff --git a/enzevalos_iphone/SwiftUI/Compose/RecipientsModel.swift b/enzevalos_iphone/SwiftUI/Compose/RecipientsModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..7003e831ba947eb8d717b01b7fe3c2e93f62766d --- /dev/null +++ b/enzevalos_iphone/SwiftUI/Compose/RecipientsModel.swift @@ -0,0 +1,72 @@ +// +// SelectReceiverModel.swift +// enzevalos_iphone +// +// Created by Oliver Wiese on 09.11.20. +// Copyright © 2020 fu-berlin. All rights reserved. +// + +import Foundation +import Combine +import SwiftUI + +/// Type of recipient field (to, cc, bcc). +enum RecipientType { + case to + case cc + case bcc + + var asString: LocalizedStringKey { + switch self { + case .to: + return "To" + case .cc: + return "Cc" + case .bcc: + return "Bcc" + } + } +} + +/// Holds models for "To", "Cc", and "Bcc" fields. +class RecipientsModel: ObservableObject { + let toModel: RecipientFieldModel + let ccModel: RecipientFieldModel + let bccModel: RecipientFieldModel + + /// Initializes models for "To", "Cc", and "Bcc" fields. + init() { + let dataprovider = LetterboxModel.instance.dataProvider + toModel = RecipientFieldModel(type: .to, parent: nil, dataprovider: dataprovider) + ccModel = RecipientFieldModel(type: .cc, parent: nil, dataprovider: dataprovider) + bccModel = RecipientFieldModel(type: .bcc, parent: nil, dataprovider: dataprovider) + } + + /// Used to deactivate Send button if email has no recipients. + var hasNoRecipients: Bool { + toModel.selectedContacts.isEmpty && + ccModel.selectedContacts.isEmpty && + bccModel.selectedContacts.isEmpty + } + + /// String array of email addresses in "To" field. + var toEMails: [String] { + get { + toModel.selectedContacts.map({$0.email}) + } + } + + /// String array of email addresses in "Cc" field. + var ccEMails: [String] { + get { + ccModel.selectedContacts.map({$0.email}) + } + } + + /// String array of email addresses in "Bcc" field. + var bccEMails: [String] { + get { + bccModel.selectedContacts.map({$0.email}) + } + } +} diff --git a/enzevalos_iphone/SwiftUI/Compose/SelectReceiverModel.swift b/enzevalos_iphone/SwiftUI/Compose/SelectReceiverModel.swift deleted file mode 100644 index 69a8c9b7b20384fdbcb2fe6151b323a2491cca6d..0000000000000000000000000000000000000000 --- a/enzevalos_iphone/SwiftUI/Compose/SelectReceiverModel.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// SelectReceiverModel.swift -// enzevalos_iphone -// -// Created by Oliver Wiese on 09.11.20. -// Copyright © 2020 fu-berlin. All rights reserved. -// - -import Foundation -import Combine -import SwiftUI - -enum TokenFieldType { - case TO, CC, BCC - - var localizedStringKey: LocalizedStringKey { - switch self { - case .TO: - return "To" - case .CC: - return "Cc" - case .BCC: - return "Bcc" - } - } -} - -class SelectReceiverModel: ObservableObject { - - let toModel: TokenFieldModel - let ccModel: TokenFieldModel - let bccModel: TokenFieldModel - - init() { - toModel = TokenFieldModel(type: .TO, parent: nil, dataprovider: LetterboxModel.instance.dataProvider) - ccModel = TokenFieldModel(type: .CC, parent: nil, dataprovider: LetterboxModel.instance.dataProvider) - bccModel = TokenFieldModel(type: .BCC, parent: nil, dataprovider: LetterboxModel.instance.dataProvider) - toModel.selectReceiverModel = self - ccModel.selectReceiverModel = self - bccModel.selectReceiverModel = self - } - - var toEMails: [String] { - get { - return toModel.selectedContacts.map({$0.email}) - } - } - - var ccEMails: [String] { - get { - return ccModel.selectedContacts.map({$0.email}) - - } - } - - var bccEMails: [String] { - get { - return bccModel.selectedContacts.map({$0.email}) - - } - } -} diff --git a/enzevalos_iphone/SwiftUI/Compose/SelectReceiverView.swift b/enzevalos_iphone/SwiftUI/Compose/SelectReceiverView.swift deleted file mode 100644 index a5d327791a08b92ef694214737d5a9e4614ad7f2..0000000000000000000000000000000000000000 --- a/enzevalos_iphone/SwiftUI/Compose/SelectReceiverView.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// SelectReceiverView.swift -// enzevalos_iphone -// -// Created by Oliver Wiese on 31.10.20. -// Copyright © 2020 fu-berlin. All rights reserved. -// - -import SwiftUI - -struct SelectReceiverView: View { - @ObservedObject var model: ComposeModel - - var body: some View { - ScrollView{ - HStack { - Text("Subject") - Text(":") - TextField("", text: $model.subject) - } - Divider() - TokenField(viewModel: model.receiverModel.toModel) - Divider() - TokenField(viewModel: model.receiverModel.ccModel) - Divider() - TokenField(viewModel: model.receiverModel.bccModel) - } - } -} - -struct SelectReceiverView_Previews: PreviewProvider { - static var previews: some View { - SelectReceiverView(model: ComposeModel(preData: nil)) - } -} diff --git a/enzevalos_iphone/SwiftUI/Compose/TokenField.swift b/enzevalos_iphone/SwiftUI/Compose/TokenField.swift deleted file mode 100644 index 4dbc87480c8af0ff327b139a85fbe38230b7ab8e..0000000000000000000000000000000000000000 --- a/enzevalos_iphone/SwiftUI/Compose/TokenField.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// TokenField.swift -// enzevalos_iphone -// -// Created by Oliver Wiese on 31.10.20. -// Copyright © 2020 fu-berlin. All rights reserved. -// - -import SwiftUI - -struct TokenField: View { - - @ObservedObject var viewModel: TokenFieldModel - - var body: some View { - field - } - var field: some View { - VStack(alignment: .leading){ - HStack{ - Text(viewModel.type.localizedStringKey) - Text(":") - TextField("", text: $viewModel.text, onCommit: { - viewModel.commitText() - }) - .keyboardType(.emailAddress) - } - ForEach(viewModel.selectedContacts.indices, id: \.self) {i in - AddressTokenView(contact: viewModel.selectedContacts[i], extended: false) - .onTapGesture { - viewModel.unselectContactAt(i: i) - } - } - if viewModel.suggestions.count > 0 { - Divider() - Text("Compose.Suggestions") - .font(.footnote) - ForEach(viewModel.suggestions, id: \.self) {contact in - AddressTokenView(contact: contact, extended: true) - .listRowBackground(Color.clear) - .onTapGesture(count: /*@START_MENU_TOKEN@*/1/*@END_MENU_TOKEN@*/, perform: { - viewModel.selectContact(addr: contact) - }) - } - } - - } - } -} - - - - -struct ClearButton: ViewModifier -{ - @Binding var text: String - - public func body(content: Content) -> some View - { - ZStack(alignment: .leading) - { - content - - if !text.isEmpty - { - Button(action: - { - self.text = "" - }) - { - Image(systemName: "delete.left") - .foregroundColor(Color(UIColor.opaqueSeparator)) - } - .padding(.trailing, 8) - } - } - } -} - -struct TokenField_Previews: PreviewProvider { - static var previews: some View { - TokenField(viewModel: TokenFieldModel(type: .TO, parent: nil, dataprovider: PersitentDataProvider.proxyPersistentDataProvider)) - } -} diff --git a/enzevalos_iphone/SwiftUI/Contact/ContactTabView.swift b/enzevalos_iphone/SwiftUI/Contact/ContactTabView.swift index 6e400a4a86e87530152512ba6ddc83e14718917e..4c6fd44cc1f90e04daf24b1461793fe8a7a61af8 100644 --- a/enzevalos_iphone/SwiftUI/Contact/ContactTabView.swift +++ b/enzevalos_iphone/SwiftUI/Contact/ContactTabView.swift @@ -38,7 +38,7 @@ struct ContactTabView <C: DisplayContact, M: DisplayMail>: View { } private var icon: some View { - contact.myImage + contact.avatar .resizable() .frame(width: 130, height: 110) .clipShape(Circle()) @@ -115,9 +115,6 @@ struct ContactTabView <C: DisplayContact, M: DisplayMail>: View { } - - - struct ContactTabView_Previews: PreviewProvider { static var previews: some View { ContactTabView<ProxyContact, ProxyMail>(contact: ProxyData.Alice) diff --git a/enzevalos_iphone/SwiftUI/Contact/ContactView.swift b/enzevalos_iphone/SwiftUI/Contact/ContactView.swift index 9aa96309808f79e4bab044a93d4a4d7cb4d51988..aba0935a46e7feef5a54f9c86071afe5dfcb191c 100644 --- a/enzevalos_iphone/SwiftUI/Contact/ContactView.swift +++ b/enzevalos_iphone/SwiftUI/Contact/ContactView.swift @@ -15,10 +15,6 @@ struct ContactView <C: DisplayContact, M: DisplayMail>: View { public var fromMail: M? public var derivedFromKey: Bool = true -// init(contact: C, fromMail: M? = nil) { -// self.contact = contact -// } - var body: some View { TabView{ ForEach(Tabs, id: \.id ){ tab in diff --git a/enzevalos_iphone/SwiftUI/DisplayProtocols.swift b/enzevalos_iphone/SwiftUI/DisplayProtocols.swift index 77b1778f0e7384a8f96809d7a1c63a371185f81f..65b5181471b5854798b82dd4749c6ff078ccf6f1 100644 --- a/enzevalos_iphone/SwiftUI/DisplayProtocols.swift +++ b/enzevalos_iphone/SwiftUI/DisplayProtocols.swift @@ -8,18 +8,25 @@ import Foundation import SwiftUI -//TODO DOcumentView?? StateObject +// TODO: DocumentView?? StateObject /* Here are protocols to display an email, key, address and a contact */ /// Enums enum FolderType { - case Archive, Trash, Inbox, Sent, Draft, Other + case Archive + case Trash + case Inbox + case Sent + case Draft + case Other } enum ResponseType { - case Reply, Forward, Draft + case Reply + case Forward + case Draft func addPrefix(subject: String) -> String { switch self { @@ -38,7 +45,14 @@ enum ResponseType { } enum CryptoState { - case UnableToDecrypt, InvalidSignature, NoCrypto, PlainMissingPublicKeyToVerify, PlainButValidSignature, EncValidSign, EncNoSignature, EncButMissingPublicKeyToVerify + case UnableToDecrypt + case InvalidSignature + case NoCrypto + case PlainMissingPublicKeyToVerify + case PlainButValidSignature + case EncValidSign + case EncNoSignature + case EncButMissingPublicKeyToVerify var buttonActions: [ButtonAction] { get { @@ -54,12 +68,11 @@ enum CryptoState { } } } - - } enum ContactSecurityRating { - case Trustworthy, Forgable + case Trustworthy + case Forgable var name: LocalizedStringKey { switch self { @@ -82,7 +95,6 @@ enum ContactSecurityRating { protocol DisplayKey { - var keyID: String { get } var discovery: Date? { get } var lastSeen: Date? { get } @@ -108,17 +120,17 @@ protocol DisplayAttachment { protocol DisplayContact { associatedtype K: DisplayKey - + // General var name: String { get } var email: String { get } - var myImage: Image { get } + var avatar: Image { get } var isInContactBook: Bool { get } var discovery: Date? { get } var last: Date? { get } // Crypto related - //var keys: [K] { get } + // var keys: [K] { get } var primaryKey: K? { get } // Phishing related @@ -126,15 +138,13 @@ protocol DisplayContact { var previousResponses: Int { get } var hasSimilarContacts: Bool { get } var similarContacts: [String] { get } - - } extension DisplayContact { func findAddress(temp: Bool) -> AddressRecord { - if let addr = PersitentDataProvider.dataProvider.fetchedAddressResultController.fetchedObjects?.first { + if let addr = PersistentDataProvider.dataProvider.fetchedAddressResultController.fetchedObjects?.first { return addr } fatalError("MISSING ADDR") @@ -178,18 +188,18 @@ protocol DisplayMail { var encryptionType: CryptoScheme? { get } var transportEnc: Bool { get } var signatureKey: K? { get } - + var persistentMail: MailRecord? { get } func markAsRead(isRead: Bool) } extension DisplayMail { - + /* TODO: MORE DIALOGS - //else if (mail.isNewPubKey) {} - // message contained new public key - */ + //else if (mail.isNewPubKey) {} + // message contained new public key + */ var signatureType: CryptoScheme? { get { @@ -201,14 +211,14 @@ extension DisplayMail { get { var key = "" switch evaluateSecurity() { - case (_,.NoCrypto): key = "Security.Dialog.Title.No.Crypto" - case (_, .UnableToDecrypt): key = "Security.Dialog.Title.UnableToDecrypt" - case (_, .InvalidSignature): key = "Security.Dialog.Title.InvalidSignature" - case (_, .PlainMissingPublicKeyToVerify): key = "Security.Dialog.Title.PlainMissingPublicKeyToVerify" - case (_, .PlainButValidSignature): key = "Security.Dialog.Title.Plain+Sig" - case (_, .EncValidSign): key = "Security.Dialog.Title.Enc+Sign" - case (_, .EncNoSignature): key = "Security.Dialog.Title.Enc+NoSign" - case (_, .EncButMissingPublicKeyToVerify): key = "Security.Dialog.Title.EncMissingPublicKeyToVerify" + case (_,.NoCrypto): key = "Security.Dialog.Title.No.Crypto" + case (_, .UnableToDecrypt): key = "Security.Dialog.Title.UnableToDecrypt" + case (_, .InvalidSignature): key = "Security.Dialog.Title.InvalidSignature" + case (_, .PlainMissingPublicKeyToVerify): key = "Security.Dialog.Title.PlainMissingPublicKeyToVerify" + case (_, .PlainButValidSignature): key = "Security.Dialog.Title.Plain+Sig" + case (_, .EncValidSign): key = "Security.Dialog.Title.Enc+Sign" + case (_, .EncNoSignature): key = "Security.Dialog.Title.Enc+NoSign" + case (_, .EncButMissingPublicKeyToVerify): key = "Security.Dialog.Title.EncMissingPublicKeyToVerify" } return NSLocalizedString(key, comment: "") } @@ -260,8 +270,8 @@ extension DisplayMail { case (_, .EncValidSign): color = ThemeManager.encryptedMessageColor() bodyKey = "OnboardingIntroSection.Confidential.description" - // ctaButton -> Ok - + // ctaButton -> Ok + case (_, .UnableToDecrypt): color = ThemeManager.troubleMessageColor() bodyKey = "ReceiveInsecureInfoDecryptionFailed" @@ -357,16 +367,16 @@ extension DisplayMail { get { var result = [DialogStruct] () switch evaluateSecurity() { - case (_, .EncValidSign), (_, .NoCrypto), (_, .PlainButValidSignature), (_, .EncNoSignature): - break // No warning required - case (_, .UnableToDecrypt), (_, .InvalidSignature) , (_, .PlainMissingPublicKeyToVerify), (_, .EncButMissingPublicKeyToVerify): - result.append(dialog) // warning required + case (_, .EncValidSign), (_, .NoCrypto), (_, .PlainButValidSignature), (_, .EncNoSignature): + break // No warning required + case (_, .UnableToDecrypt), (_, .InvalidSignature) , (_, .PlainMissingPublicKeyToVerify), (_, .EncButMissingPublicKeyToVerify): + result.append(dialog) // warning required } if self.sender.primaryKey != nil && self.signatureState == .NoSignature { let icon = Image(systemName: "exclamationmark.triangl.fill") let missingSignature = DialogStruct(dialogColor: Color(ThemeManager.unencryptedMessageColor()), title: NSLocalizedString("encryptedBeforeHeadline", - comment: "encrypted by sender before"), + comment: "encrypted by sender before"), body: NSLocalizedString("encryptedBeforeText", comment: "encrypted by sender before"), img: icon, messageImage: icon, ctaButtonTitle: NSLocalizedString("Security.Dialog.Button.Title.Confirmation", comment: ""), @@ -391,18 +401,18 @@ extension DisplayMail { private func evaluateCryptoState() -> CryptoState { switch (self.encryptionState, self.signatureState) { - // General error cases -> cast other states - case (.UnableToDecrypt, _): return .UnableToDecrypt - case (_, .InvalidSignature): return .InvalidSignature - case (.NoEncryption, .NoSignature): return .NoCrypto - case (.NoEncryption, .NoPublicKey): return .PlainMissingPublicKeyToVerify - case (.NoEncryption, .ValidSignature): return .PlainButValidSignature - case (.ValidEncryptedWithOldKey, .ValidSignature): return .EncValidSign - case (.ValidedEncryptedWithCurrentKey, .ValidSignature): return .EncValidSign - case (.ValidEncryptedWithOldKey, .NoSignature): return .EncNoSignature - case (.ValidEncryptedWithOldKey, .NoPublicKey): return .EncButMissingPublicKeyToVerify - case (.ValidedEncryptedWithCurrentKey, .NoSignature): return .EncNoSignature - case (.ValidedEncryptedWithCurrentKey, .NoPublicKey): return .EncButMissingPublicKeyToVerify + // General error cases -> cast other states + case (.UnableToDecrypt, _): return .UnableToDecrypt + case (_, .InvalidSignature): return .InvalidSignature + case (.NoEncryption, .NoSignature): return .NoCrypto + case (.NoEncryption, .NoPublicKey): return .PlainMissingPublicKeyToVerify + case (.NoEncryption, .ValidSignature): return .PlainButValidSignature + case (.ValidEncryptedWithOldKey, .ValidSignature): return .EncValidSign + case (.ValidedEncryptedWithCurrentKey, .ValidSignature): return .EncValidSign + case (.ValidEncryptedWithOldKey, .NoSignature): return .EncNoSignature + case (.ValidEncryptedWithOldKey, .NoPublicKey): return .EncButMissingPublicKeyToVerify + case (.ValidedEncryptedWithCurrentKey, .NoSignature): return .EncNoSignature + case (.ValidedEncryptedWithCurrentKey, .NoPublicKey): return .EncButMissingPublicKeyToVerify } } @@ -423,15 +433,15 @@ extension DisplayMail { if self.ccs.count > 0 { body.append("\n\(NSLocalizedString("Cc", comment: "")): ") for addr in self.ccs { - body.append("\(addr.email), ") } + body.append("\(addr.email), ") } } body.append("\n" + NSLocalizedString("subject", comment: "describing what subject was choosen") + ": " + (self.subject)) body.append("\n------------------------\n\n" + (self.body)) body = body.components(separatedBy: "\n").map { line in - if line.hasPrefix(">") { - return ">" + line - } - return "> " + line + if line.hasPrefix(">") { + return ">" + line + } + return "> " + line }.reduce("", { $0 + "\n" + $1 }) return body } diff --git a/enzevalos_iphone/SwiftUI/FolderView/FolderOverView.swift b/enzevalos_iphone/SwiftUI/FolderView/FolderOverView.swift index 5588bf090ed537378df98c69ce1b9305ce4a06d1..68b61ae2e41e06d78d86d3f1ad3028f17689a8a2 100644 --- a/enzevalos_iphone/SwiftUI/FolderView/FolderOverView.swift +++ b/enzevalos_iphone/SwiftUI/FolderView/FolderOverView.swift @@ -18,7 +18,7 @@ struct FolderOverView: View { List (folders, id: \.self){ f in NavigationLink( destination: MailListView(folderpath: f.path ?? f.name, name: f.name) - .environment(\.managedObjectContext, PersitentDataProvider.dataProvider.persistentContainer.viewContext) + .environment(\.managedObjectContext, PersistentDataProvider.dataProvider.persistentContainer.viewContext) ) { FolderRow(folder: f) } diff --git a/enzevalos_iphone/SwiftUI/Inbox/InboxView.swift b/enzevalos_iphone/SwiftUI/Inbox/InboxView.swift index 9a325ef00721d22580055de8d5a3377b62ff1753..21718661e2173dee237c91668cc9674117541859 100644 --- a/enzevalos_iphone/SwiftUI/Inbox/InboxView.swift +++ b/enzevalos_iphone/SwiftUI/Inbox/InboxView.swift @@ -20,7 +20,7 @@ struct InboxView: View { var body: some View { NavigationView{ MailListView(folderpath: folderpath, name: name) - .environment(\.managedObjectContext, PersitentDataProvider.dataProvider.persistentContainer.viewContext) + .environment(\.managedObjectContext, PersistentDataProvider.dataProvider.persistentContainer.viewContext) .navigationBarItems(leading: self.folderButton, trailing: keyManagementButton) } } @@ -41,7 +41,7 @@ struct InboxView: View { }) .background( NavigationLink(destination: FolderOverView() - .environment(\.managedObjectContext, PersitentDataProvider.dataProvider.persistentContainer.viewContext), isActive: $goToFolders) { + .environment(\.managedObjectContext, PersistentDataProvider.dataProvider.persistentContainer.viewContext), isActive: $goToFolders) { EmptyView() } .hidden() @@ -55,6 +55,6 @@ struct InboxView: View { struct InboxView_Previews: PreviewProvider { static var previews: some View { return InboxView(folderpath: "INBOX", name: "INBOX") - .environment(\.managedObjectContext, PersitentDataProvider.proxyPersistentDataProvider.persistentContainer.viewContext) + .environment(\.managedObjectContext, PersistentDataProvider.proxyPersistentDataProvider.persistentContainer.viewContext) } } diff --git a/enzevalos_iphone/SwiftUI/Inbox/KeyRecordRow.swift b/enzevalos_iphone/SwiftUI/Inbox/KeyRecordRow.swift index 0257eb51cd62277cd2e2f01de434fe3131a7145f..6677f12dc8be85dcd9056f7b1c386a92466e9f32 100644 --- a/enzevalos_iphone/SwiftUI/Inbox/KeyRecordRow.swift +++ b/enzevalos_iphone/SwiftUI/Inbox/KeyRecordRow.swift @@ -70,7 +70,7 @@ struct KeyRecordRow <M: DisplayMail, C: DisplayContact>: View { } private var icon: some View{ - self.keyrecord.myImage + self.keyrecord.avatar .resizable() .frame(width: 60, height: 60) .shadow(radius: 5) diff --git a/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift b/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift index 0d28a4bf62535e1a2dc783730557a2e88fa56522..a6abf0237ae3a968529a1878cdf479872af72adf 100644 --- a/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift +++ b/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift @@ -32,26 +32,26 @@ struct MailListView: View { self.name = name } var body: some View { - mainView + mainView .navigationBarTitle(name, displayMode: .inline) - - .onAppear(perform: { - self.updateMails() - }) - - .sheet(isPresented: $composeMail, content: { - ComposeView() - }) + + .onAppear(perform: { + self.updateMails() + }) + .sheet(isPresented: $composeMail) { + ComposeView() + } } private var mainView: some View { VStack (alignment: .leading){ SearchView(searchText: $searchText, searchField: $searchField, searchNow: $searchNow) - .padding(6) + .padding(6) mailList .padding(-10) // Toolbar HStack { + self.idButton Spacer() self.lastUpdate Spacer() @@ -67,8 +67,14 @@ struct MailListView: View { destination: ReadMainView(model: ReadModel(mail: record))) { MailRow(mail: record) } - } - .resignKeyboardOnDragGesture() // hide keyboard when dragging + } + .resignKeyboardOnDragGesture() // hide keyboard when dragging + } + + private var idButton: some View { + Button(action: { print("Go to my id") }) { + Text(NSLocalizedString("KeyID", comment: "id")) + } } private var composeButton: some View { @@ -116,14 +122,11 @@ struct MailListView: View { let query = self.searchText.lowercased() 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 @@ -133,6 +136,6 @@ struct MailListView: View { struct MailListView_Previews: PreviewProvider { static var previews: some View { MailListView(folderpath: "INBOX", name: "INBOX") - .environment(\.managedObjectContext, PersitentDataProvider.proxyPersistentDataProvider.persistentContainer.viewContext) + .environment(\.managedObjectContext, PersistentDataProvider.proxyPersistentDataProvider.persistentContainer.viewContext) } } diff --git a/enzevalos_iphone/SwiftUI/KeyManagement/KeyManagementOverview.swift b/enzevalos_iphone/SwiftUI/KeyManagement/KeyManagementOverview.swift index a750c97ac19e19efada2a4fce73b6c758685575f..e1d4dc04cb2aff08bc51f345514c8d6c09a0eba4 100644 --- a/enzevalos_iphone/SwiftUI/KeyManagement/KeyManagementOverview.swift +++ b/enzevalos_iphone/SwiftUI/KeyManagement/KeyManagementOverview.swift @@ -16,12 +16,12 @@ struct KeyManagementOverview: View { Divider() // Other keys NavigationLink("Management.Name.Crypto.Other", destination: PublicKeyListView() - .environment(\.managedObjectContext, PersitentDataProvider.dataProvider.persistentContainer.viewContext)) + .environment(\.managedObjectContext, PersistentDataProvider.dataProvider.persistentContainer.viewContext)) Divider() // My keys NavigationLink("Management.Crypto.Name.You", destination: SecretKeyListView() - .environment(\.managedObjectContext, PersitentDataProvider.dataProvider.persistentContainer.viewContext)) + .environment(\.managedObjectContext, PersistentDataProvider.dataProvider.persistentContainer.viewContext)) Divider() // Import keys NavigationLink("Management.Name.Crypto.Import", destination: MixedKeyListView()) diff --git a/enzevalos_iphone/SwiftUI/KeyManagement/PublicKeyListView.swift b/enzevalos_iphone/SwiftUI/KeyManagement/PublicKeyListView.swift index 8401155f85d936f2dd3ca4daa33094369e49fe3d..9743e94d01d6005b1b87eebf09c837ed87bd9d44 100644 --- a/enzevalos_iphone/SwiftUI/KeyManagement/PublicKeyListView.swift +++ b/enzevalos_iphone/SwiftUI/KeyManagement/PublicKeyListView.swift @@ -27,7 +27,7 @@ struct PublicKeyListView: View { } struct KeyListView_Previews: PreviewProvider { - static let context = PersitentDataProvider.proxyPersistentDataProvider.persistentContainer.viewContext + static let context = PersistentDataProvider.proxyPersistentDataProvider.persistentContainer.viewContext static var previews: some View { PublicKeyListView() diff --git a/enzevalos_iphone/SwiftUI/LetterboxApp.swift b/enzevalos_iphone/SwiftUI/LetterboxApp.swift index 33fb76ee3666724e86de6bba8945b70a2aaba822..2f93ce7c6d7ef383f0341e69894cfabc64428a46 100644 --- a/enzevalos_iphone/SwiftUI/LetterboxApp.swift +++ b/enzevalos_iphone/SwiftUI/LetterboxApp.swift @@ -22,12 +22,17 @@ struct LetterboxApp: App { } else { switch model.currentState { - case .PERMISSIONS: PermissionRequestView() - case .FIRSTLAUNCH: NewOnboardingView() - case .ACCOUNTSETTING: NewOnboardingView() - case .GENERATEKEYS: PermissionRequestView() // TODO Wait - case .LAUNCHEDBEFORE: InboxView(folderpath: MailHandler.INBOX, name: NSLocalizedString("Inbox", comment: "Inbox")) - .environment(\.managedObjectContext, PersitentDataProvider.dataProvider.persistentContainer.viewContext) + case .PERMISSIONS: + PermissionRequestView() + case .FIRSTLAUNCH: + NewOnboardingView() + case .ACCOUNTSETTING: + NewOnboardingView() + case .GENERATEKEYS: + PermissionRequestView() // TODO Wait + case .LAUNCHEDBEFORE: + InboxView(folderpath: MailHandler.INBOX, name: NSLocalizedString("Inbox", comment: "Inbox")) + .environment(\.managedObjectContext, PersistentDataProvider.dataProvider.persistentContainer.viewContext) } } } diff --git a/enzevalos_iphone/SwiftUI/LetterboxModel.swift b/enzevalos_iphone/SwiftUI/LetterboxModel.swift index 2649e192a994beb739ad08a9a4620644d8a23505..b8322b6662d502b9fc0d37fb67979a77a9d5cc94 100644 --- a/enzevalos_iphone/SwiftUI/LetterboxModel.swift +++ b/enzevalos_iphone/SwiftUI/LetterboxModel.swift @@ -35,12 +35,14 @@ enum ReachabilityStatus { } enum UIState { - case FIRSTLAUNCH, ACCOUNTSETTING, PERMISSIONS, GENERATEKEYS, LAUNCHEDBEFORE + case FIRSTLAUNCH + case ACCOUNTSETTING + case PERMISSIONS + case GENERATEKEYS + case LAUNCHEDBEFORE } class LetterboxModel: ObservableObject { - - static var instance: LetterboxModel { get { if let instance = currentIntstance { @@ -52,14 +54,11 @@ class LetterboxModel: ObservableObject { private static var currentIntstance: LetterboxModel? let mailHandler = MailHandler() - let dataProvider = PersitentDataProvider.dataProvider + let dataProvider = PersistentDataProvider.dataProvider @Published var currentState: UIState = .FIRSTLAUNCH private var keysGenerated = false - - - //TODO Refactor: Where to move this? static var currentReachabilityStatus: ReachabilityStatus { @@ -139,7 +138,7 @@ class LetterboxModel: ObservableObject { func resetApp() { if UserDefaults.standard.bool(forKey: "reset") { - PersitentDataProvider.dataProvider.deleteAll(completionHandler: { error in + PersistentDataProvider.dataProvider.deleteAll(completionHandler: { error in print("Delete All data") // Reset Keychain! @@ -163,7 +162,7 @@ class LetterboxModel: ObservableObject { private func setupKeys() { - let provider = PersitentDataProvider.dataProvider + let provider = PersistentDataProvider.dataProvider guard let userAddr = UserManager.loadUserValue(Attribute.userAddr) as? String else { fatalError("Missing user address") } diff --git a/enzevalos_iphone/SwiftUI/Read/ReadMainView.swift b/enzevalos_iphone/SwiftUI/Read/ReadMainView.swift index 97c8e89c85f9dbb798290889e144fc8c6e6eacc9..18f403310708ab18dd55e675eb9fd8dc251a7f0d 100644 --- a/enzevalos_iphone/SwiftUI/Read/ReadMainView.swift +++ b/enzevalos_iphone/SwiftUI/Read/ReadMainView.swift @@ -74,16 +74,16 @@ struct ReadMainView <M: DisplayMail>: View { } #if DEBUG -struct Layout_Previews: PreviewProvider { - static let simulator = Simulators<ReadMainView<ProxyMail>>() - static let deviceNames: [String] = [ - "iPhone SE", - "iPhone 11 Pro Max" - ] - - static var previews: some View { - simulator.previews(view: ReadMainView(model: ReadModel(mail: ProxyData.SecureMail))) - } -} +//struct Layout_Previews: PreviewProvider { +// static let simulator = Simulators<ReadMainView<ProxyMail>>() +// static let deviceNames: [String] = [ +// "iPhone SE", +// "iPhone 11 Pro Max" +// ] +// +// static var previews: some View { +// simulator.previews(view: ReadMainView(composeModel: ReadModel(mail: ProxyData.SecureMail))) +// } +//} #endif diff --git a/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewChildren/SmallContactListView.swift b/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewChildren/SmallContactListView.swift index 1c574ac0ca37b441bdbb5f09e518eb5d9b2b05bb..9c8b20ec7d5054cb25c867c0d59866f6c6c1bb6f 100644 --- a/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewChildren/SmallContactListView.swift +++ b/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewChildren/SmallContactListView.swift @@ -12,28 +12,29 @@ struct SmallContactListView <C: DisplayContact>: View { let contacts: [C] var title: String @State var showList = true - + var body: some View { - VStack (alignment: .leading, spacing: 10){ + VStack (alignment: .leading, spacing: 10){ HStack { Text(title) .font(.headline) Button (action: { - self.showList.toggle() - }) { - - Image(systemName: showList ? "chevron.up" : "chevron.down") - .resizable() - .frame(width: 12.0, height: 7.0) - } + self.showList.toggle() + }) { + + Image(systemName: showList ? "chevron.up" : "chevron.down") + .resizable() + .frame(width: 12.0, height: 7.0) + } Spacer() } + if showList { ForEach(contacts, id: \.email) {contact in NavigationLink(destination: ContactView<C, MailRecord>(contact: contact, fromMail: nil)) { HStack { - CircleImage(image: contact.myImage, radius: 40) + CircleImage(image: contact.avatar, radius: 40) VStack (alignment: .leading, spacing: 2){ Text(contact.name) .font(.headline) @@ -42,13 +43,21 @@ struct SmallContactListView <C: DisplayContact>: View { } Spacer() Image(systemName: "chevron.right") - } } + } } } } .padding(10) } + + private func goToContact(contact: C) { + /* TODOguard let con = contact.keyRecord else { + return + } */ + return + // AppDelegate.getAppDelegate().readViewCoordinator?.pushContactView(contact: con) + } } diff --git a/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewMain.swift b/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewMain.swift index de1f69c47b90a27a261a68e5c7f59fbd5919b58b..5426af55d95a15bb985fc8c0210abf361465485b 100644 --- a/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewMain.swift +++ b/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewMain.swift @@ -23,11 +23,10 @@ import SwiftUI import CoreLocation struct SenderViewMain <M: DisplayMail>: View { - // SEE: PersonNameComponents @EnvironmentObject var model: ReadModel<M> - + @State var selectedLandmark: Landmark? = nil @State var showingLandmarkDetails = false @@ -74,7 +73,7 @@ struct SenderViewMain <M: DisplayMail>: View { private var icon: some View { NavigationLink(destination: ContactView(contact: model.mail.sender, fromMail: model.mail)) { - model.mail.sender.myImage + model.mail.sender.avatar .resizable() .frame(width: 130, height: 110) .clipShape(Circle()) @@ -85,6 +84,7 @@ struct SenderViewMain <M: DisplayMail>: View { } } + private var sender: some View { VStack (alignment: .leading, spacing: 10) { Text(NSLocalizedString("Sender", comment: "Sender")) @@ -105,13 +105,13 @@ struct SenderViewMain <M: DisplayMail>: View { HStack{ Text(String(format: NSLocalizedString("ReadView.Sender.Previous", comment: "100 previous received mails"), model.mail.sender.previousMails)) - Spacer() + Spacer() Text(String(format: NSLocalizedString("ReadView.Sender.Responses", comment: "5 previous sent mails"), model.mail.sender.previousResponses)) // TODO: Add last mail date // TODO: Go to mails? } } - .padding(10) + .padding(10) } private var map: some View { @@ -151,9 +151,17 @@ struct SenderViewMain <M: DisplayMail>: View { } .alert(isPresented: $showingLandmarkDetails) { /// alert displays the landmark details and gets trigered when the user taps the information button of a landmark Alert(title: Text("Domain for this location"), message: Text(selectedLandmark?.domain ?? "Missing place information"), dismissButton: .default(Text("OK")) ) - } + } } } + + private func goToContact(contact: M.C) { + /* guard let con = contact.keyRecord else { + print("No record...") + return + } + AppDelegate.getAppDelegate().readViewCoordinator?.pushContactView(contact: con) */ + } } struct SenderView_Previews: PreviewProvider { diff --git a/enzevalos_iphone/SwiftUI/SupportingViews/MailRow.swift b/enzevalos_iphone/SwiftUI/SupportingViews/MailRow.swift index bfac19944bb7d676d4866aac803648f1c93d8883..a5169df7ddab18f55fc9fb4950bdb1ecf963f465 100644 --- a/enzevalos_iphone/SwiftUI/SupportingViews/MailRow.swift +++ b/enzevalos_iphone/SwiftUI/SupportingViews/MailRow.swift @@ -8,7 +8,7 @@ import SwiftUI -struct MailRow <M: DisplayMail>: View { +struct MailRow <M: DisplayMail>: View { let mail: M @State var isLinkActive = false @@ -30,7 +30,7 @@ struct MailRow <M: DisplayMail>: View { } private var icon: some View{ - mail.sender.myImage + mail.sender.avatar .resizable() .frame(width: 60, height: 60) .shadow(radius: 5) diff --git a/enzevalos_iphone/TempKey.swift b/enzevalos_iphone/TempKey.swift index 4efb625100b18050790878e45c76a243b29f1eb3..f9a4f0f9f2e9c7b3ee73269334e8f8b146df2541 100644 --- a/enzevalos_iphone/TempKey.swift +++ b/enzevalos_iphone/TempKey.swift @@ -91,7 +91,7 @@ class TempKey: Hashable { } func isNew() -> Bool { - guard let res = PersitentDataProvider.dataProvider.generateFetchedPublicKeysResultController(keyIds: [keyID])?.fetchedObjects else { + guard let res = PersistentDataProvider.dataProvider.generateFetchedPublicKeysResultController(keyIds: [keyID])?.fetchedObjects else { return true } if res.count == 0 { @@ -107,7 +107,7 @@ class TempKey: Hashable { func save() -> Bool { let keyIDs = SwiftPGP().store(tempKeys: [self]) - let provider = PersitentDataProvider.dataProvider + let provider = PersistentDataProvider.dataProvider if let id = keyIDs.first { let property = SecretKeyProperties(fingerprint: id, cryptoProtocol: type, usedAddresses: mailAddresses.map({AddressProperties(email: $0)})) provider.importSecretKeys(secretProperties: [property], origin: .FileTransfer, completionHandler: { @@ -126,7 +126,7 @@ class TempKey: Hashable { let property = PublicKeyProperties(fingerprint: key, cryptoProtocol: self.type, origin: .FileTransfer, preferEncryption: nil, lastSeenInAutocryptHeader: nil, lastSeenSignedMail: nil, secretKeyProperty: nil, usedAddresses: mailAddresses.map({AddressProperties(email: $0)})) keys.append(property) } - PersitentDataProvider.dataProvider.importNewData(from: keys, completionHandler: {error in print("Import done: \(String(describing: error))")}) + PersistentDataProvider.dataProvider.importNewData(from: keys, completionHandler: {error in print("Import done: \(String(describing: error))")}) } } diff --git a/enzevalos_iphone/de.lproj/Localizable.strings b/enzevalos_iphone/de.lproj/Localizable.strings index 1ed3481bf5e0dab216ef3c2ede056b2a469cb9ab..9645bd31ec6059083cfd1c261c2c42d24bfe969f 100644 --- a/enzevalos_iphone/de.lproj/Localizable.strings +++ b/enzevalos_iphone/de.lproj/Localizable.strings @@ -20,9 +20,9 @@ Du kannst das jederzeit ändern. In der Einstellungen -> Mitteilungen -> Letterb "Attachment" = "Anhang"; "Authentification" = "Authentifizierung"; "Back" = "Zurück"; -"Bcc" = "Blindkopie"; +"Bcc" = "Bcc"; "Cancel" = "Abbrechen"; -"Cc" = "Kopie"; +"Cc" = "Cc"; "Compose.Suggestions" = "Empfehlungen"; "Checkmarks" = "Du hast Nachrichten von den Adressen mit Haken bekommen"; "Close" = "Schließen"; diff --git a/enzevalos_iphone/en.lproj/Localizable.strings b/enzevalos_iphone/en.lproj/Localizable.strings index 2a4afb112b63cd79ea0e7e4bde7771d101dac3f2..bad3e4d9db6f943d5ccbde29fbff0fba8e90e209 100644 --- a/enzevalos_iphone/en.lproj/Localizable.strings +++ b/enzevalos_iphone/en.lproj/Localizable.strings @@ -19,9 +19,9 @@ "Attachment" = "Attachment"; "Authentification" = "Authentification"; "Back" = "Back"; -"Bcc" = "BCC"; +"Bcc" = "Bcc"; "Cancel" = "Cancel"; -"Cc" = "CC"; +"Cc" = "Cc"; "Compose.Suggestions" = "Suggestions"; "Checkmarks" = "You received mails from addresses with checkmarks"; // ???? "Close" = "Close"; diff --git a/enzevalos_iphone/mail/IncomingMail.swift b/enzevalos_iphone/mail/IncomingMail.swift index a19c2cdce48b943960ea97cbd2d953d72db8fb7d..93e71adf431535a93c2a641cd65428e55276c7ed 100644 --- a/enzevalos_iphone/mail/IncomingMail.swift +++ b/enzevalos_iphone/mail/IncomingMail.swift @@ -9,7 +9,11 @@ import Foundation enum PGPPart { - case MESSAGE, PUBLICKEY, SIGNATURE, SECRETKEY, SIGNEDMESSAGE; + case MESSAGE + case PUBLICKEY + case SIGNATURE + case SECRETKEY + case SIGNEDMESSAGE var start: String { get { @@ -90,7 +94,10 @@ enum PGPPart { } enum PgpMIMEType { - case SIGNATURE, ENCRYPTED, OCTET, KEYS; + case SIGNATURE + case ENCRYPTED + case OCTET + case KEYS static let allValues = [PgpMIMEType.ENCRYPTED, PgpMIMEType.KEYS, PgpMIMEType.OCTET, PgpMIMEType.SIGNATURE] @@ -147,7 +154,7 @@ class IncomingMail { keyIds.append(contentsOf: newAutocrypPublicKeys) keyIds.append(contentsOf: newPublicKeys) if let fromAdr = from?.mailbox{ - if let adr = PersitentDataProvider.dataProvider.generateFetchedAddressResultsController(address: fromAdr).fetchedObjects?.first { + if let adr = PersistentDataProvider.dataProvider.generateFetchedAddressResultsController(address: fromAdr).fetchedObjects?.first { for id in adr.keyIDs { keyIds.append(id) } @@ -202,9 +209,10 @@ class IncomingMail { return msgParser.data() } } + var decryptionKeyIDs: [String] { get { - let secretkeys = PersitentDataProvider.dataProvider.fetchedSecretKeyResultsController.fetchedObjects ?? [] + let secretkeys = PersistentDataProvider.dataProvider.fetchedSecretKeyResultsController.fetchedObjects ?? [] var decIds = [String]() for sk in secretkeys { if let id = sk.fingerprint { @@ -236,7 +244,6 @@ class IncomingMail { } } - init(rawData: Data, uID: UInt64, folderPath: String, flags: MCOMessageFlag){ self.rawData = rawData self.uID = uID @@ -321,7 +328,7 @@ class IncomingMail { if smimeObj.signatureState == .ValidSignature, let signatureKeyFingerprint = smimeObj.signKey, let sender = self.from?.mailbox { // Valid signature -> add signature key to sender let property = PublicKeyProperties(fingerprint: signatureKeyFingerprint, cryptoProtocol: .SMIME, origin: .MailAttachment, preferEncryption: nil, lastSeenInAutocryptHeader: nil, lastSeenSignedMail: self.date, secretKeyProperty: nil, usedAddresses: [AddressProperties(email: sender)]) - PersitentDataProvider.dataProvider.importNewData(from: [property], completionHandler: {(error) in print(error ?? "")}) + PersistentDataProvider.dataProvider.importNewData(from: [property], completionHandler: {(error) in print(error ?? "")}) } msgParser = MCOMessageParser(data: smimeObj.decryptedData) diff --git a/enzevalos_iphone/persistentData/AddressRecord+CoreDataClass.swift b/enzevalos_iphone/persistentData/AddressRecord+CoreDataClass.swift index 646c761a1bc754f7521d98c575efd27446d68967..4fada48daab834e10b188b2c0552261f3a40e2b4 100644 --- a/enzevalos_iphone/persistentData/AddressRecord+CoreDataClass.swift +++ b/enzevalos_iphone/persistentData/AddressRecord+CoreDataClass.swift @@ -10,6 +10,6 @@ import Foundation import CoreData @objc(AddressRecord) -public class AddressRecord: NSManagedObject { - +public class AddressRecord: NSManagedObject, Identifiable { + public let id = UUID() } diff --git a/enzevalos_iphone/persistentData/AddressRecord.swift b/enzevalos_iphone/persistentData/AddressRecord.swift index 8813cf8998291cab77007f3d73a38487524e2ef2..a43ac022d2f79f3e5de4a29727ce1d547750ebd2 100644 --- a/enzevalos_iphone/persistentData/AddressRecord.swift +++ b/enzevalos_iphone/persistentData/AddressRecord.swift @@ -7,16 +7,29 @@ // /* TODO: usedKeys -> associted? -*/ + */ import Contacts import SwiftUI import CoreData extension AddressRecord { - static var sorting: [NSSortDescriptor] { + /// Enum defining which key to to sort contacts by (name, recency). + enum SortBy: String { + case name = "displayname" + case recency = "last" // still unavailable + } + + private static var sorting: [NSSortDescriptor] { return [NSSortDescriptor(key: "email", ascending: true)] } + var mcoAddress: MCOAddress { + get { + let name = displayname ?? email + return MCOAddress.init(displayName: name, mailbox: email) + } + } + static func lookForPrefix(prefix: String) -> NSFetchRequest<AddressRecord> { let freq = NSFetchRequest<AddressRecord>(entityName: AddressRecord.entityName) freq.sortDescriptors = sorting @@ -32,16 +45,35 @@ extension AddressRecord { freq.sortDescriptors = sorting freq.fetchLimit = 30 freq.propertiesToFetch = ["email", "displayname"] - let predicate = NSPredicate(format: "email == %@" , email.lowercased()) + let predicate = NSPredicate(format: "email == %@", email.lowercased()) freq.predicate = predicate return freq } + + static func contains(_ text: String) -> NSFetchRequest<AddressRecord> { + let freq = NSFetchRequest<AddressRecord>(entityName: AddressRecord.entityName) + freq.sortDescriptors = sorting + freq.fetchLimit = 30 + freq.propertiesToFetch = ["email", "displayname"] + let predicate = NSPredicate(format: "email CONTAINS %@", text.lowercased()) + freq.predicate = predicate + return freq + } + + static func all(sortBy: SortBy) -> NSFetchRequest<AddressRecord> { + let freq = NSFetchRequest<AddressRecord>(entityName: AddressRecord.entityName) + freq.sortDescriptors = [NSSortDescriptor(key: sortBy.rawValue, ascending: true)] + freq.fetchLimit = 200 + freq.propertiesToFetch = ["email", "displayname"] + return freq + } } extension AddressRecord { static var entityName = "AddressRecord" - func update(with addressProperties: AddressProperties){ + /// Updates AddressRecord with information from AddressProperties. + func update(with addressProperties: AddressProperties) { email = addressProperties.email.lowercased() if let name = addressProperties.name { self.displayname = name @@ -50,6 +82,9 @@ extension AddressRecord { public var name: String { get { + if let contact = self.phoneBookID { + // TODO Look up cn contact name! + } if let displayname = self.displayname { return displayname } @@ -67,6 +102,7 @@ extension AddressRecord { return mails } } + private func mailsFromSet(set: NSSet?) -> [MailRecord] { if let set = set, let mails = set.allObjects as? [MailRecord] { return mails @@ -94,7 +130,7 @@ extension AddressRecord { extension AddressRecord: DisplayContact { typealias C = AddressRecord - var myImage: Image { + var avatar: Image { return ContactHandler.getImageOrDefault(addr: self) } @@ -143,19 +179,32 @@ extension AddressRecord: DisplayContact { var similarContacts: [String] { return [] } + var discovery: Date? { return nil //TODO } + + /// Date of last correspondence. var last: Date? { return nil // TODO } -} - -extension AddressRecord { - var mcoAddress: MCOAddress { - get { - let name = displayname ?? email - return MCOAddress.init(displayName: name, mailbox: email) + + /// Casual string for recency of correspondence. + var lastHeardFrom: String { + if let lastHeard = last { + let now = Date() + switch now.timeIntervalSince(lastHeard) { + case 0..<259200: + return "recently" + case 259200..<604800: + return "last week" + case 259200..<2592000: + return "last month" + default: + return "a long time ago" + } + } else { + return "never" } } } diff --git a/enzevalos_iphone/persistentData/FolderRecord.swift b/enzevalos_iphone/persistentData/FolderRecord.swift index 56dad10d999dda137a7606f27604d132bcc23084..668199e9ecd2a5e12b97087a86656f4c0f5eeb2c 100644 --- a/enzevalos_iphone/persistentData/FolderRecord.swift +++ b/enzevalos_iphone/persistentData/FolderRecord.swift @@ -71,7 +71,7 @@ extension FolderRecord { } static func lastDate(folder: String, date: Date) { - try? PersitentDataProvider.dataProvider.updateFolder(folder: folder, performingUpdates: {$0.lastUpdate = date}) + try? PersistentDataProvider.dataProvider.updateFolder(folder: folder, performingUpdates: {$0.lastUpdate = date}) } } diff --git a/enzevalos_iphone/persistentData/MailRecord.swift b/enzevalos_iphone/persistentData/MailRecord.swift index 2dc354313b983d1d378b3e735dc9a903f5dd7637..c107320e5c63db63b663b8881ab7bfd312648fcc 100644 --- a/enzevalos_iphone/persistentData/MailRecord.swift +++ b/enzevalos_iphone/persistentData/MailRecord.swift @@ -211,7 +211,7 @@ extension MailRecord: DisplayMail { var newFlag = self.messageFlag newFlag.insert(.seen) flag = Int16(newFlag.rawValue) - try? PersitentDataProvider.dataProvider.save(taskContext: context) // <- later? + try? PersistentDataProvider.dataProvider.save(taskContext: context) // <- later? } } diff --git a/enzevalos_iphone/persistentData/PersistentDataProvider.swift b/enzevalos_iphone/persistentData/PersistentDataProvider.swift index f2785dd162a7f7d1b94c17a2953d7e2b8c712a38..7f2994f299ee793b21db39e769a4a1f1d000dbba 100644 --- a/enzevalos_iphone/persistentData/PersistentDataProvider.swift +++ b/enzevalos_iphone/persistentData/PersistentDataProvider.swift @@ -31,19 +31,14 @@ Reload mails for record loadMailsForRecord(contact!, folderPath: UserManager.bac find folder by name //TODO look for not verified signed mails. */ -class PersitentDataProvider { - static let dataProvider = PersitentDataProvider() - +class PersistentDataProvider { + static let dataProvider = PersistentDataProvider() static let FETCHLIMIT = 200 static var prefKeyID: String { get { - if let id = UserManager.loadUserValue(.prefSecretKeyID) as? String { - return id - } - if let sk = dataProvider.fetchedSecretKeyResultsController.fetchedObjects?.first, let id = sk.fingerprint { - return id - } + if let id = UserManager.loadUserValue(.prefSecretKeyID) as? String { return id } + if let sk = dataProvider.fetchedSecretKeyResultsController.fetchedObjects?.first, let id = sk.fingerprint { return id } return "" } } @@ -96,8 +91,8 @@ class PersitentDataProvider { return keys } - static var proxyPersistentDataProvider: PersitentDataProvider = { - let result = PersitentDataProvider(inMemory: true) + static var proxyPersistentDataProvider: PersistentDataProvider = { + let result = PersistentDataProvider(inMemory: true) for addr in proxyAddresses { result.importManyAddresses(addrs: proxyAddresses, taskContext: result.persistentContainer.viewContext, addTo: nil) @@ -124,9 +119,7 @@ class PersitentDataProvider { self.inMemory = inMemory } - /** - A persistent container to set up the Core Data stack. - */ + // A persistent container to set up the Core Data stack. lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "DataModel") @@ -156,9 +149,8 @@ class PersitentDataProvider { return container }() - /** - Creates and configures a private queue context. - */ + + // Creates and configures a private queue context. private func newTaskContext() -> NSManagedObjectContext { // Create a private queue context. let taskContext = persistentContainer.newBackgroundContext() @@ -451,15 +443,12 @@ class PersitentDataProvider { } catch { performError = error - } } } else { performError = PersistentDataError.missingData } - - } if let error = performError { throw error @@ -573,15 +562,21 @@ class PersitentDataProvider { lazy var fetchedPublicKeyResultsController: NSFetchedResultsController<PublicKeyRecord> = { // Create a fetch request for the email entity sorted by time. - return generateFetchResultController(enitityName: PublicKeyRecord.entityName, sortDescriptors: [NSSortDescriptor(key: "fingerprint", ascending: false)], predicate: nil, propertiesToFetch: ["fingerprint"], fetchLimit: nil, context: nil) + return generateFetchResultController(entityName: PublicKeyRecord.entityName, sortDescriptors: [NSSortDescriptor(key: "fingerprint", ascending: false)], predicate: nil, propertiesToFetch: ["fingerprint"], fetchLimit: nil, context: nil) }() - - /** sorting: [String: Bool]: -> NSSortDescriptor(key: key, ascending: value) */ - func lookUp<T: NSManagedObject> (entityName: String, sorting: [String: Bool], equalPredicates: [String: String]?, differentPredicates: [String: String]?, inPredicates: [String: [String]]?, propertiesToFetch: [String]? = nil, context: NSManagedObjectContext? = nil) throws -> NSFetchedResultsController<T> { + func lookUp<T: NSManagedObject>( + entityName: String, + sorting: [String: Bool], + equalPredicates: [String: String]?, + differentPredicates: [String: String]?, + inPredicates: [String: [String]]?, + propertiesToFetch: [String]? = nil, + context: NSManagedObjectContext? = nil) + throws -> NSFetchedResultsController<T> { let sortingDescriptor = sorting.map{(k,v) in NSSortDescriptor(key: k, ascending: v)} var predicates = [NSPredicate]() @@ -624,11 +619,18 @@ class PersitentDataProvider { else if predicates.count > 1 { predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates) } - return generateFetchResultController(enitityName: entityName, sortDescriptors: sortingDescriptor, predicate: predicate, propertiesToFetch: propertiesToFetch, fetchLimit: PersitentDataProvider.FETCHLIMIT, context: context) + return generateFetchResultController(entityName: entityName, sortDescriptors: sortingDescriptor, predicate: predicate, propertiesToFetch: propertiesToFetch, fetchLimit: PersistentDataProvider.FETCHLIMIT, context: context) } - private func generateFetchResultController<T: NSManagedObject> (enitityName: String, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?, propertiesToFetch: [String]?, fetchLimit: Int?, context: NSManagedObjectContext?) -> NSFetchedResultsController<T> { - let freq = NSFetchRequest<T>(entityName: enitityName) + private func generateFetchResultController<T: NSManagedObject>( + entityName: String, + sortDescriptors: [NSSortDescriptor], + predicate: NSPredicate?, + propertiesToFetch: [String]?, + fetchLimit: Int?, + context: NSManagedObjectContext?) + -> NSFetchedResultsController<T> { + let freq = NSFetchRequest<T>(entityName: entityName) freq.sortDescriptors = sortDescriptors freq.predicate = predicate freq.propertiesToFetch = propertiesToFetch @@ -654,7 +656,10 @@ class PersitentDataProvider { return controller } - func createFetchResultController<T: NSManagedObject> (fetchRequest: NSFetchRequest<T>, inViewContext: Bool = true) -> NSFetchedResultsController<T> { + func createFetchResultController<T: NSManagedObject>( + fetchRequest: NSFetchRequest<T>, + inViewContext: Bool = true) + -> NSFetchedResultsController<T> { let context = inViewContext ? persistentContainer.viewContext : persistentContainer.newBackgroundContext() let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil) // Perform the fetch. @@ -669,7 +674,7 @@ class PersitentDataProvider { func generateFetchedMailsInFolderResultsController(folderpath: String) -> NSFetchedResultsController<MailRecord> { let sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)] let predicate = NSPredicate(format: "inFolder.path == %@", folderpath) - return generateFetchResultController(enitityName: MailRecord.entityName, sortDescriptors: sortDescriptors, predicate: predicate, propertiesToFetch: nil, fetchLimit: PersitentDataProvider.FETCHLIMIT, context: nil) + return generateFetchResultController(entityName: MailRecord.entityName, sortDescriptors: sortDescriptors, predicate: predicate, propertiesToFetch: nil, fetchLimit: PersistentDataProvider.FETCHLIMIT, context: nil) } func generateFetchedFolderResultsController(folderpath: String) -> NSFetchedResultsController<FolderRecord> { @@ -721,7 +726,7 @@ class PersitentDataProvider { let sortingDescriptor = NSSortDescriptor(key: "date", ascending: true) let predicate = NSPredicate(format: "encryptionStateInt == \(EncryptionState.UnableToDecrypt.rawValue)") - return generateFetchResultController(enitityName: MailRecord.entityName, sortDescriptors: [sortingDescriptor], predicate: predicate, propertiesToFetch: nil, fetchLimit: PersitentDataProvider.FETCHLIMIT, context: nil) + return generateFetchResultController(entityName: MailRecord.entityName, sortDescriptors: [sortingDescriptor], predicate: predicate, propertiesToFetch: nil, fetchLimit: PersistentDataProvider.FETCHLIMIT, context: nil) } func importSecretKeys(secretProperties: [SecretKeyProperties], origin: Origin, completionHandler: @escaping (Error?) -> Void) { @@ -832,6 +837,4 @@ extension NSManagedObjectContext { } return nil } - - } diff --git a/enzevalos_iphone/persistentData/SecretKeyRecord.swift b/enzevalos_iphone/persistentData/SecretKeyRecord.swift index 890a128c4d0fe477a1a388f43ceb5174f8d753b0..9c9e699e3dcc5f38aca53ea8952f5943ea02eb03 100644 --- a/enzevalos_iphone/persistentData/SecretKeyRecord.swift +++ b/enzevalos_iphone/persistentData/SecretKeyRecord.swift @@ -49,7 +49,7 @@ extension SecretKeyRecord: DisplayKey { } var isPreferedKey: Bool { - return PersitentDataProvider.prefKeyID == self.fingerprint + return PersistentDataProvider.prefKeyID == self.fingerprint } var signedMailsCounter: Int { diff --git a/enzevalos_iphoneTests/testMails/signedSMIMEfromMac 2.eml b/enzevalos_iphoneTests/testMails/signedSMIMEfromMac 2.eml new file mode 100644 index 0000000000000000000000000000000000000000..91f00a0843a257cefbb83336e307cd4500b7af8e --- /dev/null +++ b/enzevalos_iphoneTests/testMails/signedSMIMEfromMac 2.eml @@ -0,0 +1,121 @@ +From: Oliver Wiese <oliver.wiese@fu-berlin.de> +Content-Type: multipart/signed; + boundary="Apple-Mail=_58C9D0E1-2B0A-4882-8F02-411E8FCB5BF0"; + protocol="application/pkcs7-signature"; + micalg=sha-256 +Mime-Version: 1.0 (Mac OS X Mail 13.4 \(3608.120.23.2.4\)) +Subject: A signed smime mail from mac +X-Universally-Unique-Identifier: 4DCBCCEE-E252-4369-B8DA-8390C62F2AF3 +Message-Id: <D1D41054-2FAC-4B10-B692-5F3D296BA1E9@fu-berlin.de> +Date: Tue, 16 Feb 2021 17:16:58 +0100 +To: bob@enzevalos.de + + +--Apple-Mail=_58C9D0E1-2B0A-4882-8F02-411E8FCB5BF0 +Content-Transfer-Encoding: 7bit +Content-Type: text/plain; + charset=us-ascii + +A signed smime mail from mac +--Apple-Mail=_58C9D0E1-2B0A-4882-8F02-411E8FCB5BF0 +Content-Disposition: attachment; + filename=smime.p7s +Content-Type: application/pkcs7-signature; + name=smime.p7s +Content-Transfer-Encoding: base64 + +MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAKCCEQcw +ggUSMIID+qADAgECAgkA4wvV+K8l2YEwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAkRFMSsw +KQYDVQQKDCJULVN5c3RlbXMgRW50ZXJwcmlzZSBTZXJ2aWNlcyBHbWJIMR8wHQYDVQQLDBZULVN5 +c3RlbXMgVHJ1c3QgQ2VudGVyMSUwIwYDVQQDDBxULVRlbGVTZWMgR2xvYmFsUm9vdCBDbGFzcyAy +MB4XDTE2MDIyMjEzMzgyMloXDTMxMDIyMjIzNTk1OVowgZUxCzAJBgNVBAYTAkRFMUUwQwYDVQQK +EzxWZXJlaW4genVyIEZvZXJkZXJ1bmcgZWluZXMgRGV1dHNjaGVuIEZvcnNjaHVuZ3NuZXR6ZXMg +ZS4gVi4xEDAOBgNVBAsTB0RGTi1QS0kxLTArBgNVBAMTJERGTi1WZXJlaW4gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMtg1/9moUHN0vqH +l4pzq5lN6mc5WqFggEcVToyVsuXPztNXS43O+FZsFVV2B+pG/cgDRWM+cNSrVICxI5y+NyipCf8F +XRgPxJiZN7Mg9mZ4F4fCnQ7MSjLnFp2uDo0peQcAIFTcFV9Kltd4tjTTwXS1nem/wHdN6r1ZB+Ba +L2w8pQDcNb1lDY9/Mm3yWmpLYgHurDg0WUU2SQXaeMpqbVvAgWsRzNI8qIv4cRrKO+KA3Ra0Z3qL +NupOkSk9s1FcragMvp0049ENF4N1xDkesJQLEvHVaY4l9Lg9K7/AjsMeO6W/VRCrKq4Xl14zzsjz +9AkH4wKGMUZrAcUQDBHHWekCAwEAAaOCAXQwggFwMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU +k+PYMiba1fFKpZFK4OpL4qIMz+EwHwYDVR0jBBgwFoAUv1kgNgB5oKAia4zV8mHSuCzLgkowEgYD +VR0TAQH/BAgwBgEB/wIBAjAzBgNVHSAELDAqMA8GDSsGAQQBga0hgiwBAQQwDQYLKwYBBAGBrSGC +LB4wCAYGZ4EMAQICMEwGA1UdHwRFMEMwQaA/oD2GO2h0dHA6Ly9wa2kwMzM2LnRlbGVzZWMuZGUv +cmwvVGVsZVNlY19HbG9iYWxSb290X0NsYXNzXzIuY3JsMIGGBggrBgEFBQcBAQR6MHgwLAYIKwYB +BQUHMAGGIGh0dHA6Ly9vY3NwMDMzNi50ZWxlc2VjLmRlL29jc3ByMEgGCCsGAQUFBzAChjxodHRw +Oi8vcGtpMDMzNi50ZWxlc2VjLmRlL2NydC9UZWxlU2VjX0dsb2JhbFJvb3RfQ2xhc3NfMi5jZXIw +DQYJKoZIhvcNAQELBQADggEBAIcL/z4Cm2XIVi3WO5qYi3FP2ropqiH5Ri71sqQPrhE4eTizDnS6 +dl2e6BiClmLbTDPo3flq3zK9LExHYFV/53RrtCyD2HlrtrdNUAtmB7Xts5et6u5/MOaZ/SLick0+ +hFvu+c+Z6n/XUjkurJgARH5pO7917tALOxrN5fcPImxHhPalR6D90Bo0fa3SPXez7vTXTf/D6OWS +T1k+kEcQSrCFWMBvf/iu7QhCnh7U3xQuTY+8npTD5+32GPg8SecmqKc22CzeIs2LgtjZeOJVEqM7 +h0S2EQvVDFKvaYwPBt/QolOLV5h7z/0HJPT8vcP9SpIClxvyt7bPZYoaorVyGTkwggWsMIIElKAD +AgECAgcbY7rQHiw9MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYDVQQGEwJERTFFMEMGA1UEChM8VmVy +ZWluIHp1ciBGb2VyZGVydW5nIGVpbmVzIERldXRzY2hlbiBGb3JzY2h1bmdzbmV0emVzIGUuIFYu +MRAwDgYDVQQLEwdERk4tUEtJMS0wKwYDVQQDEyRERk4tVmVyZWluIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IDIwHhcNMTYwNTI0MTEzODQwWhcNMzEwMjIyMjM1OTU5WjCBjTELMAkGA1UEBhMCREUx +RTBDBgNVBAoMPFZlcmVpbiB6dXIgRm9lcmRlcnVuZyBlaW5lcyBEZXV0c2NoZW4gRm9yc2NodW5n +c25ldHplcyBlLiBWLjEQMA4GA1UECwwHREZOLVBLSTElMCMGA1UEAwwcREZOLVZlcmVpbiBHbG9i +YWwgSXNzdWluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ07eRxH3h+Gy8Zp +1xCeOdfZojDbchwFfylfS2jxrRnWTOFrG7ELf6Gr4HuLi9gtzm6IOhDuV+UefwRRNuu6cG1joL6W +LkDh0YNMZj0cZGnlm6Stcq5oOVGHecwX064vXWNxSzl660Knl5BpBb+Q/6RAcL0D57+eGIgfn5mI +TQ5HjUhfZZkQ0tkqSe3BuS0dnxLLFdM/fx5ULzquk1enfnjK1UriGuXtQX1TX8izKvWKMKztFwUk +P7agCwf9TRqaA1KgNpzeJIdl5Of6x5ZzJBTN0OgbaJ4YWa52fvfRCng8h0uwN89Tyjo4EPPLR22M +ZD08WkVKusqAfLjz56dMTM0CAwEAAaOCAgUwggIBMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYDVR0P +AQH/BAQDAgEGMCkGA1UdIAQiMCAwDQYLKwYBBAGBrSGCLB4wDwYNKwYBBAGBrSGCLAEBBDAdBgNV +HQ4EFgQUazqYi/nyU4na4K2yMh4JH+iqO3QwHwYDVR0jBBgwFoAUk+PYMiba1fFKpZFK4OpL4qIM +z+EwgY8GA1UdHwSBhzCBhDBAoD6gPIY6aHR0cDovL2NkcDEucGNhLmRmbi5kZS9nbG9iYWwtcm9v +dC1nMi1jYS9wdWIvY3JsL2NhY3JsLmNybDBAoD6gPIY6aHR0cDovL2NkcDIucGNhLmRmbi5kZS9n +bG9iYWwtcm9vdC1nMi1jYS9wdWIvY3JsL2NhY3JsLmNybDCB3QYIKwYBBQUHAQEEgdAwgc0wMwYI +KwYBBQUHMAGGJ2h0dHA6Ly9vY3NwLnBjYS5kZm4uZGUvT0NTUC1TZXJ2ZXIvT0NTUDBKBggrBgEF +BQcwAoY+aHR0cDovL2NkcDEucGNhLmRmbi5kZS9nbG9iYWwtcm9vdC1nMi1jYS9wdWIvY2FjZXJ0 +L2NhY2VydC5jcnQwSgYIKwYBBQUHMAKGPmh0dHA6Ly9jZHAyLnBjYS5kZm4uZGUvZ2xvYmFsLXJv +b3QtZzItY2EvcHViL2NhY2VydC9jYWNlcnQuY3J0MA0GCSqGSIb3DQEBCwUAA4IBAQCBeEWkTqR/ +DlXwCbFqPnjMaDWpHPOVnj/z+N9rOHeJLI21rT7H8pTNoAauusyosa0zCLYkhmI2THhuUPDVbmCN +T1IxQ5dGdfBi5G5mUcFCMWdQ5UnnOR7Ln8qGSN4IFP8VSytmm6A4nwDO/afr0X9XLchMX9wQEZc+ +lgQCXISoKTlslPwQkgZ7nu7YRrQbtQMMONncsKk/cQYLsgMHM8KNSGMlJTx6e1du94oFOO+4oK4v +9NsH1VuEGMGpuEvObJAaguS5Pfp38dIfMwK/U+d2+dwmJUFvL6Yb+qQTkPp8ftkLYF3sv8pBoGH7 +EUkp2KgtdRXYShjqFu9VNCIaE40GMIIGPTCCBSWgAwIBAgIMHquxIGDgCGtc8ZZZMA0GCSqGSIb3 +DQEBCwUAMIGNMQswCQYDVQQGEwJERTFFMEMGA1UECgw8VmVyZWluIHp1ciBGb2VyZGVydW5nIGVp +bmVzIERldXRzY2hlbiBGb3JzY2h1bmdzbmV0emVzIGUuIFYuMRAwDgYDVQQLDAdERk4tUEtJMSUw +IwYDVQQDDBxERk4tVmVyZWluIEdsb2JhbCBJc3N1aW5nIENBMB4XDTE4MDIyMDE0NDEyMloXDTIx +MDIxOTE0NDEyMlowgZoxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl +cmxpbjEiMCAGA1UECgwZRnJlaWUgVW5pdmVyc2l0YWV0IEJlcmxpbjEuMCwGA1UECwwlRmFjaGJl +cmVpY2ggTWF0aGVtYXRpayB1bmQgSW5mb3JtYXRpazEVMBMGA1UEAwwMT2xpdmVyIFdpZXNlMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx25g+X495DeptnwCaQ6cykfU3s2nICuN+XOA +Jlvu2alLN2qaySL2vmumySejVk2jZxC3Is5I/3GrYL6uCpLjp0w7LeEkmrfRGFYV1cMOMtKz+exv +M+xplyYf4ibVYPhPGR3oHy3VDdd1QQolqmADdS4MDeOHBknEqaWDvVSkSWt2Jm5QmnVuvEDmppsA +c4C3w8IR1MOU3TnVphlIrDF6ZrMxKvxzCrFEby8jE6paipgS+ZfZxRtCaqZUWW3Vu5CWWyz1CLOu +Ff0FZOX6uG8RdBeBm7NUagg8UXkDCKX08bMIYfShz+IoCy0aHV7Ks5I3+N6/2pHEmoWm/qpdkPta +fQIDAQABo4ICjDCCAogwQAYDVR0gBDkwNzAPBg0rBgEEAYGtIYIsAQEEMBEGDysGAQQBga0hgiwB +AQQDBzARBg8rBgEEAYGtIYIsAgEEAwcwCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBeAwHQYDVR0l +BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMB0GA1UdDgQWBBTZ8ytnI7fxN4uilTbn+lUG1l6R0jAf +BgNVHSMEGDAWgBRrOpiL+fJTidrgrbIyHgkf6Ko7dDBcBgNVHREEVTBTgRlvbGl2ZXIud2llc2VA +ZnUtYmVybGluLmRlgRt3aWVzZW9saUB6ZWRhdC5mdS1iZXJsaW4uZGWBGU9saXZlci5XaWVzZUBm +dS1iZXJsaW4uZGUwgY0GA1UdHwSBhTCBgjA/oD2gO4Y5aHR0cDovL2NkcDEucGNhLmRmbi5kZS9k +Zm4tY2EtZ2xvYmFsLWcyL3B1Yi9jcmwvY2FjcmwuY3JsMD+gPaA7hjlodHRwOi8vY2RwMi5wY2Eu +ZGZuLmRlL2Rmbi1jYS1nbG9iYWwtZzIvcHViL2NybC9jYWNybC5jcmwwgdsGCCsGAQUFBwEBBIHO +MIHLMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5wY2EuZGZuLmRlL09DU1AtU2VydmVyL09DU1Aw +SQYIKwYBBQUHMAKGPWh0dHA6Ly9jZHAxLnBjYS5kZm4uZGUvZGZuLWNhLWdsb2JhbC1nMi9wdWIv +Y2FjZXJ0L2NhY2VydC5jcnQwSQYIKwYBBQUHMAKGPWh0dHA6Ly9jZHAyLnBjYS5kZm4uZGUvZGZu +LWNhLWdsb2JhbC1nMi9wdWIvY2FjZXJ0L2NhY2VydC5jcnQwDQYJKoZIhvcNAQELBQADggEBAIZ4 +78RroIuTMr5QvBVINS9oX6HFIOHCPtSuuKRMpb6OHCmyye0NySJAVtK9ozDzomkShvMzMm5SmAoG +xubcqgb5ovg8eixwDvEj+QlKQ8hQHUNoYf4ExNeU+KDE0pSE6AM1GgnRB34Xtjpk/d+6nA6JKqxz +VSH00K3dSzVtrDdgnAdr4su8pTtgYNL2hlTzqkSj0MdzcyBq7PvmVlSE60umivECuQt+Sig6HEt2 +e2bgQ52ZVvKTpQ8oTj0+Mfw2ul2eYyRcLEz9Y2/GpwLXai5QQbClPmPRo1z0mFoYYe2R8QiwHHgS +11RI6lHw4wUHox0PrfDb8vZlz2jafZQendUxggOdMIIDmQIBATCBnjCBjTELMAkGA1UEBhMCREUx +RTBDBgNVBAoMPFZlcmVpbiB6dXIgRm9lcmRlcnVuZyBlaW5lcyBEZXV0c2NoZW4gRm9yc2NodW5n +c25ldHplcyBlLiBWLjEQMA4GA1UECwwHREZOLVBLSTElMCMGA1UEAwwcREZOLVZlcmVpbiBHbG9i +YWwgSXNzdWluZyBDQQIMHquxIGDgCGtc8ZZZMA0GCWCGSAFlAwQCAQUAoIIBzzAYBgkqhkiG9w0B +CQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yMTAyMTYxNjE2NThaMC8GCSqGSIb3DQEJ +BDEiBCC8Fytiv1ZpmEb8bvmg27rVyebYhhu4n7CvuB65krlt7DCBrwYJKwYBBAGCNxAEMYGhMIGe +MIGNMQswCQYDVQQGEwJERTFFMEMGA1UECgw8VmVyZWluIHp1ciBGb2VyZGVydW5nIGVpbmVzIERl +dXRzY2hlbiBGb3JzY2h1bmdzbmV0emVzIGUuIFYuMRAwDgYDVQQLDAdERk4tUEtJMSUwIwYDVQQD +DBxERk4tVmVyZWluIEdsb2JhbCBJc3N1aW5nIENBAgweq7EgYOAIa1zxllkwgbEGCyqGSIb3DQEJ +EAILMYGhoIGeMIGNMQswCQYDVQQGEwJERTFFMEMGA1UECgw8VmVyZWluIHp1ciBGb2VyZGVydW5n +IGVpbmVzIERldXRzY2hlbiBGb3JzY2h1bmdzbmV0emVzIGUuIFYuMRAwDgYDVQQLDAdERk4tUEtJ +MSUwIwYDVQQDDBxERk4tVmVyZWluIEdsb2JhbCBJc3N1aW5nIENBAgweq7EgYOAIa1zxllkwDQYJ +KoZIhvcNAQEBBQAEggEAMT5sbk0eX0B6kJjLa22vWULGUjKIo/klAOLzeTbejIdGy+kRxZP3Ztxy +cROX8JxHGMPslJ9lmnbmIYwCqb7JgIZjyelJNDESzPzo12krkhH0t8J3tUFC/TyKCQg1JBRNdT+L +08/7/xYJL7DOG1CXTiIQi7P29QpCGCisfuEKJ8ac3cZrT81EIfGnEoIAKam+RFlE4TDAZEBF8qgm +s6pxT8aSUjMnMP8ZtTHQpNbUsMqLkilXYsnSREleAbqptItWbJ+qsGJMcC4wc28tNGOLEsdYjGsH +eM+AIln6HACsMCpqXSkxgkc7T7F7DTlb+FwGe+UyaZOj1O3LzoS+b8jijAAAAAAAAA== +--Apple-Mail=_58C9D0E1-2B0A-4882-8F02-411E8FCB5BF0--