diff --git a/enzevalos_iphone.xcodeproj/project.pbxproj b/enzevalos_iphone.xcodeproj/project.pbxproj index 941e44e75ed7eac063b4f5710950d6f20ed6a106..a07d7a8839c273a008cda44b2887c7bd8d63d30f 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 */; }; @@ -175,9 +178,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 */; }; @@ -201,10 +202,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 */; }; @@ -309,6 +307,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>"; }; @@ -558,9 +559,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>"; }; @@ -582,10 +581,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>"; }; @@ -1253,14 +1249,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>"; @@ -1912,13 +1906,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 */, @@ -1929,7 +1922,7 @@ 47D1302B1F7CEE6D007B14DF /* DebugSettings.swift in Sources */, 47691A8C1ECC3EC7004BCFC5 /* EphemeralMail.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 */, @@ -1953,7 +1946,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 */, @@ -1982,6 +1974,7 @@ 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 */, @@ -1992,8 +1985,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 */, @@ -2007,6 +1998,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 */, @@ -2017,6 +2009,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 */, @@ -2025,7 +2018,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/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/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 312fc4d72ee8e504c3d5561393d110276ef7eeb1..cb109f0d5ee5d22cd357005d857ba79d6cb8c22b 100644 --- a/enzevalos_iphone/New Group/Mailbot.swift +++ b/enzevalos_iphone/New Group/Mailbot.swift @@ -77,11 +77,11 @@ 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 { + 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: nil, attachedPublicKeys: [], attachedSecretKeys: []) - PersitentDataProvider.dataProvider.importNewData(from: [mail], completionHandler: {error in }) + PersistentDataProvider.dataProvider.importNewData(from: [mail], completionHandler: {error in }) } } diff --git a/enzevalos_iphone/OutgoingMail.swift b/enzevalos_iphone/OutgoingMail.swift index 2cbe151e49927fb3c262d246822938c5eb04f3ef..207c0fb69283d14e1940174b47c36bf8857e443b 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 @@ -383,7 +392,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 @@ -407,35 +416,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") @@ -476,7 +484,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) @@ -486,20 +494,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 @@ -508,7 +516,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 485d6f647489240e6509b91a6e9446cded6309ab..46ee6876cc6a78a9af2f4abdbe9019f33549dea2 100644 --- a/enzevalos_iphone/PGP/Autocrypt.swift +++ b/enzevalos_iphone/PGP/Autocrypt.swift @@ -111,7 +111,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 @@ -164,5 +164,4 @@ class Autocrypt { } } - } diff --git a/enzevalos_iphone/PGP/SwiftPGP.swift b/enzevalos_iphone/PGP/SwiftPGP.swift index cd1dba8f1d06bae439d101e5c59feee18263bf0f..374876e2ee758cd9d02cdf539483d64677431c14 100644 --- a/enzevalos_iphone/PGP/SwiftPGP.swift +++ b/enzevalos_iphone/PGP/SwiftPGP.swift @@ -507,7 +507,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] = [] @@ -611,7 +611,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 { @@ -627,7 +627,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!") } @@ -668,7 +668,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/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..cd6853f1bc3130bb4e670676eb4ae54f67d39b52 --- /dev/null +++ b/enzevalos_iphone/SwiftUI/Compose/RecipientListView.swift @@ -0,0 +1,47 @@ +// +// RecipientListView.swift +// letterbox_prototyping +// +// Created by Chris Offner 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..fb0f3ba38fd7f8597143b16a0154c1109e6887e4 --- /dev/null +++ b/enzevalos_iphone/SwiftUI/Compose/RecipientRowView.swift @@ -0,0 +1,62 @@ +// +// recipientRowView.swift +// letterbox_prototyping +// +// Created by Chris Offner 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 e7fbcb9b8acbc412be600be4ca8cefca29b5b6f5..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) - }) - } - } - - } - } -} - - - -// Code is unused. -//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 60a8383a9db9f25cda7577bc53f3ce417ac88512..5e2f7c6de7a1d47d73b8611c3011725a3b3538e2 100644 --- a/enzevalos_iphone/SwiftUI/Contact/ContactTabView.swift +++ b/enzevalos_iphone/SwiftUI/Contact/ContactTabView.swift @@ -37,7 +37,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()) @@ -114,9 +114,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/DisplayProtocols.swift b/enzevalos_iphone/SwiftUI/DisplayProtocols.swift index 4af400ede769dea7d1ed96a5f78dfb33888ba461..f7fde6e9c7033c09f9f5f3d4b337012f98179e20 100644 --- a/enzevalos_iphone/SwiftUI/DisplayProtocols.swift +++ b/enzevalos_iphone/SwiftUI/DisplayProtocols.swift @@ -112,13 +112,13 @@ protocol DisplayContact { // 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 +126,13 @@ protocol DisplayContact { var previousResponses: Int { get } var hasSimilarContacts: Bool { get } var similarContacts: [String] { get } - - } extension DisplayContact { func findAddress(temp: Bool) -> MailAddress { - if let addr = PersitentDataProvider.dataProvider.fetchedAddressResultController.fetchedObjects?.first { + if let addr = PersistentDataProvider.dataProvider.fetchedAddressResultController.fetchedObjects?.first { return addr } fatalError("MISSING ADDR") 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 a326d7cec1413a3ac356c6c4eddc0aead964a4e6..159ae41bc56b1595aeafbb8544500b4bd8dab6fd 100644 --- a/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift +++ b/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift @@ -38,9 +38,9 @@ struct MailListView: View { .onAppear(perform: { self.updateMails() }) - .sheet(isPresented: $composeMail, content: { + .sheet(isPresented: $composeMail) { ComposeView() - }) + } } private var mainView: some View { @@ -141,6 +141,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 d1d59343e88418f85c0ba59f5ba13340b7520286..e2a4a7f4b5eee497b4f82a64cd350ca2cd030f05 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 { @@ -140,7 +139,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! @@ -164,7 +163,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 6eaecfe5be0d59a6a2a89e2b524f4e60b43c0909..b52e468ac095650a3ef73d6a848135d66963a130 100644 --- a/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewChildren/SmallContactListView.swift +++ b/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewChildren/SmallContactListView.swift @@ -33,7 +33,7 @@ struct SmallContactListView <C: DisplayContact>: View { ForEach(contacts, id: \.email) {contact in Group { HStack { - CircleImage(image: contact.myImage, radius: 40) + CircleImage(image: contact.avatar, radius: 40) VStack (alignment: .leading, spacing: 2){ Text(contact.name) .font(.subheadline) diff --git a/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewMain.swift b/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewMain.swift index b1f6f14b217836e545e62aaaa29f4532d3278346..a5f2c9a1c1f26a455ad01e2dc76d7c1a6616014f 100644 --- a/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewMain.swift +++ b/enzevalos_iphone/SwiftUI/Read/Tabbed Views/SenderViewMain.swift @@ -73,7 +73,7 @@ struct SenderViewMain <M: DisplayMail>: View { } private var icon: some View { - model.mail.sender.myImage + model.mail.sender.avatar .resizable() .frame(width: 130, height: 110) .clipShape(Circle()) 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 1d4721925be2423f03146ad66c88d56056823a0e..fb85676d368b7ae3847df401862b69ed5ded0bbd 100644 --- a/enzevalos_iphone/de.lproj/Localizable.strings +++ b/enzevalos_iphone/de.lproj/Localizable.strings @@ -15,9 +15,9 @@ "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 faf44a87b5a8dd0e24358b294ae70dd1c66d8405..a94dfd6d71ee62aa54b149a699853d3d55f9a1bf 100644 --- a/enzevalos_iphone/en.lproj/Localizable.strings +++ b/enzevalos_iphone/en.lproj/Localizable.strings @@ -15,9 +15,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 83f59bb5a2ff510cabecd42394919f91c756e2f9..5fea5bf3ac517ea8e14498c8d4d04af09a360189 100644 --- a/enzevalos_iphone/mail/IncomingMail.swift +++ b/enzevalos_iphone/mail/IncomingMail.swift @@ -147,7 +147,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 k in adr.publicKeys { if let id = k.fingerprint { keyIds.append(id) @@ -206,7 +206,7 @@ class IncomingMail { } 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 { @@ -299,7 +299,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 8c8cf8441748c03e86b0c7137449948585c2c23e..afbec27072b03aed852ae179f1526e683f6e4fdb 100644 --- a/enzevalos_iphone/persistentData/AddressRecord.swift +++ b/enzevalos_iphone/persistentData/AddressRecord.swift @@ -13,7 +13,12 @@ import SwiftUI import CoreData extension AddressRecord { - static var sorting: [NSSortDescriptor] { + enum SortBy: String { + case name = "displayname" + case recency = "last" // still unavailable + } + + private static var sorting: [NSSortDescriptor] { return [NSSortDescriptor(key: "email", ascending: true)] } @@ -32,16 +37,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 @@ -71,6 +95,7 @@ extension AddressRecord { return mails } } + private func mailsFromSet(set: NSSet?) -> [MailRecord] { if let set = set, let mails = set.allObjects as? [MailRecord] { return mails @@ -116,8 +141,6 @@ extension AddressRecord: MailAddress { public var contact: Contact? { return nil // TODO } - - } extension AddressRecord: Contact { @@ -134,7 +157,6 @@ extension AddressRecord: Contact { addresses.append(contentsOf: set) } } - return addresses } } @@ -142,7 +164,7 @@ extension AddressRecord: Contact { extension AddressRecord: DisplayContact { typealias C = AddressRecord - var myImage: Image { + var avatar: Image { return Image(uiImage: getImageOrDefault()) } @@ -180,10 +202,32 @@ extension AddressRecord: DisplayContact { var similarContacts: [String] { return [] } + var discovery: Date? { return nil //TODO } + + /// Date of last correspondence. var last: Date? { return nil // TODO } + + /// 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 4998d8b2a36fa7e89b21f703b24bc79fdc010cbe..5d81b0ec8e80fe58ab5a346d0f02c050aabce4c5 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() @@ -447,15 +439,12 @@ class PersitentDataProvider { } catch { performError = error - } } } else { performError = PersistentDataError.missingData } - - } if let error = performError { throw error @@ -610,7 +599,7 @@ class PersitentDataProvider { else if predicates.count > 1 { predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates) } - return generateFetchResultController(enitityName: entityName, sortDescriptors: sortingDescriptor, predicate: predicate, propertiesToFetch: nil, fetchLimit: PersitentDataProvider.FETCHLIMIT, context: context) + return generateFetchResultController(enitityName: entityName, sortDescriptors: sortingDescriptor, predicate: predicate, propertiesToFetch: nil, fetchLimit: PersistentDataProvider.FETCHLIMIT, context: context) } private func generateFetchResultController<T: NSManagedObject> (enitityName: String, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?, propertiesToFetch: [String]?, fetchLimit: Int?, context: NSManagedObjectContext?) -> NSFetchedResultsController<T> { @@ -653,7 +642,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(enitityName: MailRecord.entityName, sortDescriptors: sortDescriptors, predicate: predicate, propertiesToFetch: nil, fetchLimit: PersistentDataProvider.FETCHLIMIT, context: nil) } func generateFetchedFolderResultsController(folderpath: String) -> NSFetchedResultsController<FolderRecord> { @@ -705,7 +694,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(enitityName: MailRecord.entityName, sortDescriptors: [sortingDescriptor], predicate: predicate, propertiesToFetch: nil, fetchLimit: PersistentDataProvider.FETCHLIMIT, context: nil) } func importSecretKeys(secretProperties: [SecretKeyProperties], origin: Origin, completionHandler: @escaping (Error?) -> Void) { @@ -816,6 +805,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 {