diff --git a/enzevalos_iphone/SwiftUI/Compose/ComposeView.swift b/enzevalos_iphone/SwiftUI/Compose/ComposeView.swift index 9592800013bf53993b6eea605ac30c73b111c334..b95ef9469033be7b9d9c96300e429d371075f7f5 100644 --- a/enzevalos_iphone/SwiftUI/Compose/ComposeView.swift +++ b/enzevalos_iphone/SwiftUI/Compose/ComposeView.swift @@ -10,56 +10,41 @@ import SwiftUI /// A view used to compose and send an email. struct ComposeView: View { @State private var cc = "" - @State private var isEditingCcOrBcc = false - @ObservedObject var composer: ComposeModel + @ObservedObject var model: ComposeModel /// - Parameter preData: Data of email to reply to or forward. init(preData: PreMailData? = nil) { - composer = ComposeModel(preData: preData) + model = ComposeModel(preData: preData) } var body: some View { VStack { // Top bar with Cancel and Send button ComposeViewHeader() - .environmentObject(composer) + .environmentObject(model) Divider() - // "To" recipient - RecipientField(recipientFieldModel: composer.recipientsModel.toModel) + // "To" recipients + RecipientField(model: model.recipientsModel.toModel) Divider() - // "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() + // "Cc/Bcc" recipients + CcAndBccFields(model: model.recipientsModel) // Subject HStack { Text("Subject") .foregroundColor(Color(UIColor.tertiaryLabel)) - TextField("", text: $composer.subject) + TextField("", text: $model.subject) .autocapitalization(.none) .frame(minWidth: 0, maxWidth: .infinity) } + Divider() // Email body - TextEditor(text: $composer.body) + TextEditor(text: $model.body) } .padding() .animation(.default) @@ -67,23 +52,41 @@ struct ComposeView: View { } } +struct CcAndBccFields: View { + @ObservedObject var model: RecipientsModel + + var body: some View { + VStack { + RecipientField(model: model.ccModel) + + Divider() + + if model.showBccField { + RecipientField(model: model.bccModel) + + Divider() + } + } + } +} + /// A view in which recipients get added or removed. struct RecipientField: View { - @ObservedObject var recipientFieldModel: RecipientFieldModel + @ObservedObject var model: RecipientFieldModel @State var showList = false var body: some View { VStack { HStack { // Recipient text field - Text(recipientFieldModel.type.asString) + Text(model.type.asString) .foregroundColor(Color(UIColor.tertiaryLabel)) // Shows selected recipients as blue capsules // followed by TextField for new recipients ScrollView(.horizontal) { HStack(spacing: 4) { - ForEach(recipientFieldModel.selectedContacts) { (recipient: AddressRecord) in + ForEach(model.selectedContacts) { (recipient: AddressRecord) in Text(recipient.displayname ?? recipient.email) .foregroundColor(.white) .padding(.horizontal, 12) @@ -91,9 +94,18 @@ struct RecipientField: View { .background(Color.accentColor) .clipShape(Capsule()) } - TextField("", text: $recipientFieldModel.text, onCommit: { - recipientFieldModel.commitText() - }) + TextField("", text: $model.text) { isEditing in + model.parent?.isEditingCcOrBcc = isEditing + } onCommit: { + model.commitText() + // TODO: Fix bug on first Cc or Bcc recipient commit + // For some reason, model.selectedContacts.count stays 0 + // after the first committed recipient, leading to the Cc + // and Bcc fields getting collapsed again despite the + // first recipient clearly getting rendered as a blue + // capsule in the ForEach loop above. 🤔🤔🤔 + print(model.selectedContacts.count) + } .frame(minWidth: 200) .autocapitalization(.none) .keyboardType(.emailAddress) @@ -101,8 +113,10 @@ struct RecipientField: View { } // Toggles contact list - Button(action: { showList.toggle() }) { - Image(systemName: !showList ? "plus.circle" : "chevron.up.circle") + if model.type != .ccBcc { + Button(action: { showList.toggle() }) { + Image(systemName: !showList ? "plus.circle" : "chevron.up.circle") + } } } .frame(height: 20) @@ -111,18 +125,18 @@ struct RecipientField: View { if showList { Divider() RecipientListView() - .frame(height: 464) + .frame(height: 460) } } - .environmentObject(recipientFieldModel) + .environmentObject(model) } } /// A view containing the Cancel and Send buttons for an email. struct ComposeViewHeader: View { @Environment(\.presentationMode) var presentationMode - @EnvironmentObject var composer: ComposeModel - + @EnvironmentObject var model: ComposeModel + var body: some View { VStack { // Grab handle in top center @@ -130,7 +144,6 @@ struct ComposeViewHeader: View { .fill(Color(.lightGray)) .frame(width: 70, height: 5) - HStack { // Cancel button Button("Cancel") { @@ -140,17 +153,17 @@ struct ComposeViewHeader: View { Spacer() // Send button - Button(action: { - composer.sendMail() + Button { + model.sendMail() presentationMode.wrappedValue.dismiss() - }) { - if composer.encryptionOff { - UnencryptedSendButton(grayedOut: composer.recipientsModel.hasNoRecipients) + } label: { + if model.encryptionOff { + UnencryptedSendButton(grayedOut: model.recipientsModel.hasNoRecipients) } else { - EncryptedSendButton(grayedOut: composer.recipientsModel.hasNoRecipients) + EncryptedSendButton(grayedOut: model.recipientsModel.hasNoRecipients) } } - .disabled(composer.recipientsModel.hasNoRecipients) + .disabled(model.recipientsModel.hasNoRecipients) } } } diff --git a/enzevalos_iphone/SwiftUI/Compose/RecipientFieldModel.swift b/enzevalos_iphone/SwiftUI/Compose/RecipientFieldModel.swift index 34cba3ee041c238f0355e6dea1a1607580f9766b..c85b7b560f9f1b46aee5ef14a105cdcfb731af53 100644 --- a/enzevalos_iphone/SwiftUI/Compose/RecipientFieldModel.swift +++ b/enzevalos_iphone/SwiftUI/Compose/RecipientFieldModel.swift @@ -11,12 +11,13 @@ import SwiftUI /// A model for a single recipient field. class RecipientFieldModel: ObservableObject { - @Published var suggestions: [AddressRecord] = [] - @Published var selectedContacts = [AddressRecord]() // TODO: As AddressProperty??? + @Published var suggestions = [AddressRecord]() + @Published var selectedContacts = [AddressRecord]() + @Published var type: RecipientType private var dataprovider: PersistentDataProvider - let type: RecipientType + var parent: RecipientsModel? - init(type: RecipientType, parent: RecipientsModel?, dataprovider: PersistentDataProvider) { + init(type: RecipientType, dataprovider: PersistentDataProvider) { self.type = type self.dataprovider = dataprovider } @@ -68,16 +69,18 @@ class RecipientFieldModel: ObservableObject { addNewAddress(text) text = "" } + print(selectedContacts.count) } func addNewAddress(_ address: String) { - guard address.count > 0 else { - // TODO: Add email validiation + warning? + guard address.count > 0 && address.contains("@") else { + // TODO: Add email validation + warning? return } 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/RecipientsModel.swift b/enzevalos_iphone/SwiftUI/Compose/RecipientsModel.swift index 7003e831ba947eb8d717b01b7fe3c2e93f62766d..04b61012b62f6949715b8618a9bb96b3a3972a91 100644 --- a/enzevalos_iphone/SwiftUI/Compose/RecipientsModel.swift +++ b/enzevalos_iphone/SwiftUI/Compose/RecipientsModel.swift @@ -15,15 +15,18 @@ enum RecipientType { case to case cc case bcc + case ccBcc var asString: LocalizedStringKey { switch self { - case .to: - return "To" - case .cc: - return "Cc" - case .bcc: - return "Bcc" + case .to: + return "To" + case .cc: + return "Cc" + case .bcc: + return "Bcc" + case .ccBcc: + return "CcBcc" } } } @@ -33,13 +36,25 @@ class RecipientsModel: ObservableObject { let toModel: RecipientFieldModel let ccModel: RecipientFieldModel let bccModel: RecipientFieldModel + @Published var showBccField = false /// 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) + toModel = RecipientFieldModel(type: .to, dataprovider: dataprovider) + ccModel = RecipientFieldModel(type: .ccBcc, dataprovider: dataprovider) + bccModel = RecipientFieldModel(type: .bcc, dataprovider: dataprovider) + ccModel.parent = self + bccModel.parent = self + } + + /// Used to show or hide Bcc field + var isEditingCcOrBcc: Bool = false { + didSet { + print("isEditingCcOrBcc: \(isEditingCcOrBcc)") + updateShowBccField() + print("showBccField: \(showBccField)") + } } /// Used to deactivate Send button if email has no recipients. @@ -52,21 +67,28 @@ class RecipientsModel: ObservableObject { /// String array of email addresses in "To" field. var toEMails: [String] { get { - toModel.selectedContacts.map({$0.email}) + toModel.selectedContacts.map({ $0.email }) } } /// String array of email addresses in "Cc" field. var ccEMails: [String] { get { - ccModel.selectedContacts.map({$0.email}) + ccModel.selectedContacts.map({ $0.email }) } } /// String array of email addresses in "Bcc" field. var bccEMails: [String] { get { - bccModel.selectedContacts.map({$0.email}) + bccModel.selectedContacts.map({ $0.email }) } } + + func updateShowBccField() { + showBccField = isEditingCcOrBcc + || !ccModel.selectedContacts.isEmpty + || !bccModel.selectedContacts.isEmpty + ccModel.type = showBccField ? .cc : .ccBcc + } } diff --git a/enzevalos_iphone/de.lproj/Localizable.strings b/enzevalos_iphone/de.lproj/Localizable.strings index 09632edfcffd5824b783bfe6c3b57d575ecaa83a..dd12413601e5e878a630412d2da52cc062b53e75 100644 --- a/enzevalos_iphone/de.lproj/Localizable.strings +++ b/enzevalos_iphone/de.lproj/Localizable.strings @@ -22,6 +22,7 @@ "Bcc" = "Bcc"; "Cancel" = "Abbrechen"; "Cc" = "Cc"; +"CcBcc" = "Cc/Bcc"; "Name" = "Name"; "SortBy" = "Sortiere nach"; "LastContacted" = "zuletzt kontaktiert"; diff --git a/enzevalos_iphone/en.lproj/Localizable.strings b/enzevalos_iphone/en.lproj/Localizable.strings index a103f0e2cc94a92ee9bd1756e0540f3dd2e8af4f..4f6c447df0fc14dfe755ad33e3e6229676c3d270 100644 --- a/enzevalos_iphone/en.lproj/Localizable.strings +++ b/enzevalos_iphone/en.lproj/Localizable.strings @@ -22,6 +22,7 @@ "Bcc" = "Bcc"; "Cancel" = "Cancel"; "Cc" = "Cc"; +"CcBcc" = "Cc/Bcc"; "SortBy" = "Sort by"; "Name" = "Name"; "LastContacted" = "Last Contacted";