Commit b9a45c3e authored by Oliver Wiese's avatar Oliver Wiese
Browse files

Merge branch '298-missing-backend-features-connections-for-ui'

Conflicts:
	enzevalos_iphone/SwiftUI/Compose/ComposeModel.swift
parents 348cff0a d8f21af3
......@@ -45,6 +45,7 @@
4707092D2189C74200DF71A3 /* bobSecret.asc in Resources */ = {isa = PBXBuildFile; fileRef = 4707092B2189C74200DF71A3 /* bobSecret.asc */; };
4707092E2189C74200DF71A3 /* alicePublic.asc in Resources */ = {isa = PBXBuildFile; fileRef = 4707092C2189C74200DF71A3 /* alicePublic.asc */; };
470709302189E1C100DF71A3 /* enc+signedThunderbird.eml in Resources */ = {isa = PBXBuildFile; fileRef = 4707092F2189E1C000DF71A3 /* enc+signedThunderbird.eml */; };
4709769626220659002436E2 /* SimpleMailRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4709769526220659002436E2 /* SimpleMailRowView.swift */; };
47184C3922F0D8F200712A7A /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47184C3822F0D8F200712A7A /* CFNetwork.framework */; };
471876F7223FACA900912135 /* BobPWTEST1234.asc in Resources */ = {isa = PBXBuildFile; fileRef = 471876F5223FACA900912135 /* BobPWTEST1234.asc */; };
471876F8223FACA900912135 /* BobWithoutPW.asc in Resources */ = {isa = PBXBuildFile; fileRef = 471876F6223FACA900912135 /* BobWithoutPW.asc */; };
......@@ -325,6 +326,7 @@
4707092B2189C74200DF71A3 /* bobSecret.asc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = bobSecret.asc; sourceTree = "<group>"; };
4707092C2189C74200DF71A3 /* alicePublic.asc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = alicePublic.asc; sourceTree = "<group>"; };
4707092F2189E1C000DF71A3 /* enc+signedThunderbird.eml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "enc+signedThunderbird.eml"; sourceTree = "<group>"; };
4709769526220659002436E2 /* SimpleMailRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleMailRowView.swift; sourceTree = "<group>"; };
47184C3822F0D8F200712A7A /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
471876F5223FACA900912135 /* BobPWTEST1234.asc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = BobPWTEST1234.asc; sourceTree = "<group>"; };
471876F6223FACA900912135 /* BobWithoutPW.asc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = BobWithoutPW.asc; sourceTree = "<group>"; };
......@@ -1045,6 +1047,7 @@
isa = PBXGroup;
children = (
477120C1254C676000B28C64 /* ContactView.swift */,
4709769526220659002436E2 /* SimpleMailRowView.swift */,
32261742260C7CE60068CBD4 /* ContactMailListView.swift */,
);
path = Contact;
......@@ -1977,6 +1980,7 @@
47FAE3492524FB58005A1BCB /* AddressRecord.swift in Sources */,
4775D7AA243F0E260052F2CC /* SimulatorData.swift in Sources */,
47C8225324379EAE005BCE73 /* AttachmentsViewMain.swift in Sources */,
4709769626220659002436E2 /* SimpleMailRowView.swift in Sources */,
678942612430C3D600C746D1 /* Typosquatting.swift in Sources */,
47C112CA2531E9B000621A07 /* AttachmentRecord.swift in Sources */,
476406982416B54D00C7D426 /* CircleImage.swift in Sources */,
......
......@@ -211,12 +211,11 @@ struct LoginAdvancedSection: View{
ForEach(0..<encryptionOptions.count) { i in
Text(self.encryptionOptions[i]).tag(i)
.onTapGesture {
print("Tap!")
if !s {
self.imapEncryption = i
} else {
self.smtpEncryption = i
}
if !s {
self.imapEncryption = i
} else {
self.smtpEncryption = i
}
}
}
}
......
......@@ -24,7 +24,7 @@ import SwiftUI
// TODO: Mark displayname as from CNContact
class ContactHandler {
static var cartoons = true
static var cartoons = false
private let store = CNContactStore()
static let handler = ContactHandler()
......
......@@ -48,6 +48,10 @@
case postcard
}
enum FolderError: Error {
case WrongUidValidity
}
class MailHandler {
private static let MAXMAILS = 25
private static let extraHeaders = Autocrypt.EXTRAHEADERS
......@@ -164,7 +168,7 @@
// TODO SEND MAIL... later...
func storeIMAP(mail: OutgoingMail, folder: String, callback: ((MailServerConnectionError?) -> Void)?) {
// 1. Test if folder exists
let existFolderController = dataProvider.generateFetchedFolderResultsController(folderpath: folder)
let existFolderController = dataProvider.generateFetchedFolderResultsController(folderpath: folder, moc: nil)
if existFolderController.fetchedObjects != nil {
// 2. Store Mail in test
// We can always store encrypted data on the imap server because the user has a key pair and it is users imap account.
......@@ -191,7 +195,7 @@
return
}
// Create folder on local
let folderProperty = FolderProperties(delimiter: nil, uidValidity: nil, lastUpdate: nil, maxUID: nil, minUID: nil, path: folder)
let folderProperty = FolderProperties(delimiter: nil, uidValidity: nil, lastUpdate: nil, maxUID: nil, minUID: nil, path: folder, flags: 0)
dataProvider.importNewData(from: [folderProperty], completionHandler: { error in
guard error == nil else {
// TODO: Error handling
......@@ -277,7 +281,7 @@
guard IMAPSession != nil else {
return
}
if let f = dataProvider.generateFetchedFolderResultsController(folderpath: folderName).fetchedObjects?.first {
if let f = dataProvider.generateFetchedFolderResultsController(folderpath: folderName, moc: nil).fetchedObjects?.first {
let folderstatus = IMAPSession?.folderStatusOperation(folderName)
folderstatus?.start {[unowned self] (error, status) -> Void in
guard error == nil else {
......@@ -320,7 +324,7 @@
completionCallback(MailServerConnectionError.NoData)
return
}
if let folder = dataProvider.generateFetchedFolderResultsController(folderpath: MailHandler.INBOX).fetchedObjects?.first {
if let folder = dataProvider.generateFetchedFolderResultsController(folderpath: MailHandler.INBOX, moc: nil).fetchedObjects?.first {
let folderstatus = IMAPSession?.folderStatusOperation(folder.path)
folderstatus?.start {[unowned self] (error, status) -> Void in
guard error == nil else {
......@@ -344,7 +348,7 @@
completionCallback(0, completionHandler)
return
}
if let folder = dataProvider.generateFetchedFolderResultsController(folderpath: MailHandler.INBOX).fetchedObjects?.first {
if let folder = dataProvider.generateFetchedFolderResultsController(folderpath: MailHandler.INBOX, moc: nil).fetchedObjects?.first {
let folderstatus = IMAPSession?.folderStatusOperation(folder.path)
// Work only in background thread....
var backgroundTaskID: Int?
......@@ -448,79 +452,96 @@
}
}
}
private func createFolders(of paths: [String], completionHandler: @escaping(_ error: Error?) -> Void) {
guard let session = IMAPSession else {
completionHandler(MailServerConnectionError.ConnectionError)
return
}
session.fetchAllFoldersOperation()?.start({error, folders in
guard error != nil else {
completionHandler(error)
return
}
var myError: Error? = nil
var createFolders = [String]()
if let names = folders?.map({$0.path}) {
for path in paths {
if !names.contains(path) {
createFolders.append(path)
}
}
}
let myGroup = DispatchGroup()
let queue = DispatchQueue(label: "com.letterbox.mailhandler")
queue.async {
for path in createFolders {
myGroup.enter()
session.createFolderOperation(path)?.start({error in
myError = error
myGroup.leave()
})
}
myGroup.notify(queue: queue, execute: {
completionHandler(myError)
})
}
})
}
private func moveOnServer(uidValidity: UInt32, mails: [UInt64], fromPath: String, toPath: String, completionHandler: @escaping(_ error: Error?) -> Void) {
guard let session = IMAPSession else {
completionHandler(MailServerConnectionError.ConnectionError)
return
}
// 1. Check uid valilidity
session.folderInfoOperation(fromPath)?.start({error, infos in
guard error == nil, let infos = infos else {
completionHandler(error)
return
}
guard infos.uidValidity == uidValidity else {
completionHandler(FolderError.WrongUidValidity)
return
}
// 2. Move mails if uid is valid
let uidSet = MCOIndexSet()
mails.forEach({uidSet.add($0)})
session.moveMessagesOperation(withFolder: fromPath, uids: uidSet, destFolder: toPath)?.start({(error, _) in
guard error == nil else {
session.copyMessagesOperation(withFolder: fromPath, uids: uidSet, destFolder: toPath)?.start({(error, _) in
completionHandler(error)
})
return
}
completionHandler(nil)
return
})
})
}
func move(mails: [UInt64], fromPath: String, toPath: String) {
// 1. Check if we have to create a folder.
createFolders(of: [fromPath, toPath], completionHandler: {error in
guard error == nil else {
return
}
// 2. Move mail on server
guard let fromFolder = self.dataProvider.generateFetchedFolderResultsController(folderpath: fromPath, moc: nil).fetchedObjects?.first else {
return
}
if let uidValidity = fromFolder.uidValidity as? UInt32 {
self.moveOnServer(uidValidity: uidValidity, mails: mails, fromPath: fromPath, toPath: toPath, completionHandler: {error in
// 3. Move mail in core data
self.dataProvider.moveMails(with: mails, from: fromPath, to: toPath)
})
}
})
}
/* 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)
......@@ -536,7 +557,7 @@
if let folders = array {
var properities = [FolderProperties]()
for folder in folders {
let property = FolderProperties(delimiter: String(Character(UnicodeScalar(UInt8(folder.delimiter)))), uidValidity: nil, lastUpdate: nil, maxUID: nil, minUID: nil, path: folder.path, parent: nil, children: nil)
let property = FolderProperties(delimiter: String(Character(UnicodeScalar(UInt8(folder.delimiter)))), uidValidity: nil, lastUpdate: nil, maxUID: nil, minUID: nil, path: folder.path, parent: nil, children: nil, flags: Int16(folder.flags.rawValue))
properities.append(property)
}
self.dataProvider.importNewData(from: properities, completionHandler: completion)
......
......@@ -93,6 +93,7 @@ class Mailbot {
let mail = MailProperties(
messageID: UUID().uuidString,
uid: 0,
subject: subject, date: Date(),
flags: 0,
from: sender,
......@@ -106,7 +107,7 @@ class Mailbot {
maxUID: nil,
minUID: nil,
path: UserManager.backendInboxFolderPath,
parent: nil, children: nil),
parent: nil, children: nil, flags: 0),
body: body,
attachments: [],
signatureState: SignatureState.ValidSignature.rawValue,
......
......@@ -9,7 +9,7 @@
import Foundation
class OutgoingMail {
static var OutgoingFolder = FolderProperties(delimiter: nil, uidValidity: nil, lastUpdate: nil, maxUID: nil, minUID: nil, path: "OutgoingMails", parent: nil, children: nil)
static var OutgoingFolder = FolderProperties(delimiter: nil, uidValidity: nil, lastUpdate: nil, maxUID: nil, minUID: nil, path: "OutgoingMails", parent: nil, children: nil, flags: Int16(MCOIMAPFolderFlag.sentMail.rawValue))
private var encAddresses: [MCOAddress] = []
var encReceivers: [MCOAddress] {
......@@ -162,9 +162,9 @@ class OutgoingMail {
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
let uuid = UUID()
let from = AddressProperties(email: self.sender.mailbox, name: self.sender.displayName)
let m = MailProperties(messageID: UUID().uuidString, subject: subject, date: Date(), flags: 0, from: from, to: to, cc: cc , bcc: bcc, folder: OutgoingMail.OutgoingFolder, body: body, attachments: attachmentProperties, signatureState: sigState, encryptionState: encState, signatureKey: nil, decryptionKey: nil, autocryptHeaderKey: [], attachedPublicKeys: [], attachedSecretKeys: [])
let m = MailProperties(messageID: uuid.uuidString, uid: UInt64(arc4random()), subject: subject, date: Date(), flags: 0, from: from, to: to, cc: cc , bcc: bcc, folder: OutgoingMail.OutgoingFolder, body: body, attachments: attachmentProperties, signatureState: sigState, encryptionState: encState, signatureKey: nil, decryptionKey: nil, autocryptHeaderKey: [], attachedPublicKeys: [], attachedSecretKeys: [])
LetterboxModel.instance.dataProvider.importNewData(from: [m], completionHandler: { _ in })
// TODO Fix crypto stuff (keys etc.)
}
......@@ -190,7 +190,7 @@ class OutgoingMail {
func send(informUser: Bool = false) {
self.informUser = informUser
if let mail = mail {
LetterboxModel.instance.dataProvider.deleteMail(mail: mail)
mail.delete()
}
LetterboxModel.instance.mailHandler.sendSMTP(mail: self, callback: { error in
if error != nil {
......
......@@ -54,53 +54,50 @@ struct ComposeViewHeader: View {
.disabled(model.recipientsModel.hasNoRecipients)
}
// Encryption toggle
Toggle("", isOn: $model.encryptionOn)
.toggleStyle(EncryptionToggleStyle())
.labelsHidden()
// Encryption button
encryptionButton
}
}
/// Custom styling for the encryption toggle in ComposeViewHeader.
struct EncryptionToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
Button {
configuration.isOn.toggle()
} label: {
// Separate labels required for desired scaling transition between states
if configuration.isOn {
encryptedButtonLabel
} else {
unencryptedButtonLabel
}
/// Encryption state button view
private var encryptionButton: some View {
Button {
model.toogleEncryption()
} label: {
// Separate labels required for desired scaling transition between states
if model.encryptionOn {
encryptedButtonLabel
} else {
unencryptedButtonLabel
}
.frame(width: 40, height: 40)
}
/// Label style for encryption button when encryption is activated.
private var encryptedButtonLabel: some View {
ZStack {
Circle()
.stroke(Color.blue, lineWidth: 2)
Image(systemName: "lock.fill")
.font(Font.system(size: 24, weight: Font.Weight.light))
.foregroundColor(.blue)
}
.transition(AnyTransition.opacity.combined(with: .scale))
.frame(width: 40, height: 40)
}
/// Label style for encryption button when encryption is activated.
private var encryptedButtonLabel: some View {
ZStack {
Circle()
.stroke(Color.blue, lineWidth: 2)
Image(systemName: "lock.fill")
.font(Font.system(size: 24, weight: Font.Weight.light))
.foregroundColor(.blue)
}
/// Label style for encryption button when encryption is deactivated.
private var unencryptedButtonLabel: some View {
ZStack {
Circle()
.fill(Color(UIColor.tertiaryLabel))
Image(systemName: "lock.slash.fill")
.font(Font.system(size: 24, weight: Font.Weight.light))
.foregroundColor(.white)
}
.transition(AnyTransition.opacity.combined(with: .scale))
.transition(AnyTransition.opacity.combined(with: .scale))
}
/// Label style for encryption button when encryption is deactivated.
private var unencryptedButtonLabel: some View {
ZStack {
Circle()
.fill(Color(UIColor.tertiaryLabel))
Image(systemName: "lock.slash.fill")
.font(Font.system(size: 24, weight: Font.Weight.light))
.foregroundColor(.white)
}
.transition(AnyTransition.opacity.combined(with: .scale))
}
}
......@@ -66,6 +66,15 @@ class ComposeModel: ObservableObject {
generateMail().send()
}
/// Checks if encryption state can be toggled.
func toogleEncryption() {
if encryptionOn {
encryptionOn = false
} else {
recipientsModel.checkEncryption()
}
}
/// Adds email addresses to given RecipientFieldModel.
///
/// - Parameters:
......@@ -75,6 +84,13 @@ class ComposeModel: ObservableObject {
for address in addresses {
model.addNewAddress(address)
}
let frc = PersistentDataProvider.dataProvider.generateFetchedAddresesWithKeyResultsController(addresses: addresses)
if let records = frc.fetchedObjects {
if encryptionOn && records.count != addresses.count {
encryptionOn = false
}
}
}
/// Generates OutgoingMail with given email contents.
......
......@@ -206,7 +206,6 @@ struct RecipientField: View {
// and Bcc fields getting collapsed again despite the
// first recipient clearly getting rendered as a blue
// capsule in the ForEach loop above. 🤔🤔🤔
print(model.selectedContacts.count)
}
.frame(width: 200) // TODO: Stretch dynamically over available horizontal space
.autocapitalization(.none) // .frame(maxWidth: .infinity) somehow doesn't work
......
......@@ -82,6 +82,9 @@ class RecipientFieldModel: ObservableObject {
/// - Parameter _: AddressRecord that gets added to array of recipients.
func selectContact(_ addressRecord: AddressRecord) {
selectedContacts.append(addressRecord)
if !addressRecord.hasPublicKey {
self.parentRecipientModel?.parentComposeModel?.encryptionOn = false
}
text = ""
suggestions = []
......@@ -97,8 +100,12 @@ class RecipientFieldModel: ObservableObject {
///
/// - Parameter at: Index of contact to be removed from array of recipients.
func deselectContact(at index: Int) {
selectedContacts.remove(at: index)
let addr = selectedContacts[index]
selectedContacts.remove(at: index)
if !addr.hasPublicKey {
parentRecipientModel?.checkEncryption()
}
// TODO: See TODO in selectContact.
parentRecipientModel?.parentComposeModel?.subject += ""
}
......@@ -132,6 +139,9 @@ class RecipientFieldModel: ObservableObject {
if let addresses = frc.fetchedObjects, let addr = addresses.first {
self.selectedContacts.append(addr)
if !addr.hasPublicKey {
self.parentRecipientModel?.parentComposeModel?.encryptionOn = false
}
}
}
}
......
......@@ -44,9 +44,7 @@ class RecipientsModel: ObservableObject {
/// Used to show or hide Bcc field
var isEditingCcOrBcc: Bool = false {
didSet {
print("isEditingCcOrBcc: \(isEditingCcOrBcc)")
updateShowBccField()
print("showBccField: \(showBccField)")
}
}
......@@ -91,6 +89,26 @@ class RecipientsModel: ObservableObject {
|| !bccModel.selectedContacts.isEmpty
ccModel.type = showBccField ? .cc : .ccBcc
}
/// Turnes encryption on if all recipients have a key.
func checkEncryption() {
for addr in toModel.selectedContacts {
if !addr.hasPublicKey {
return
}
}
for addr in ccModel.selectedContacts {
if !addr.hasPublicKey {
return
}
}
for addr in bccModel.selectedContacts {
if !addr.hasPublicKey {
return
}
}
parentComposeModel?.encryptionOn = true
}
}
/// Type of recipient field (to, cc, bcc).
......
......@@ -37,7 +37,7 @@ struct ContactMailListView: View {
ForEach (0..<min(3,filteredEmails.count)) { record in
NavigationLink(
destination: ReadMainView(model: ReadModel(mail: filteredEmails[record]))) {
MailRowView(mail: filteredEmails[record], activateOnTap: false)
SimpleMailRowView(mail: filteredEmails[record])
}
if record < min(2, filteredEmails.count - 1) {
Divider()
......
......@@ -30,7 +30,6 @@ struct ContactView <C: DisplayContact>: View {
VStack {
HStack {
avatar
VStack (alignment: .leading) {
contactName.padding(.bottom)
knownSince
......@@ -144,7 +143,7 @@ struct ContactView <C: DisplayContact>: View {
} label: {
Text("\(eachAddress)")
.fontWeight(.light)
.font(.title2)
.font(.body)
.foregroundColor(.blue)
.truncationMode(.middle)
.frame(maxWidth: .infinity,
......@@ -157,32 +156,11 @@ struct ContactView <C: DisplayContact>: View {
// This row shows the security rating and a button do display the keys
HStack{
rating
Button {
withAnimation{