Skip to content

Commit

Permalink
fix: Use new route to set signature + use defaultReplySignature (#1395)
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinperignon authored May 1, 2024
2 parents 409bc9d + c7793c7 commit 330e7be
Show file tree
Hide file tree
Showing 15 changed files with 134 additions and 94 deletions.
5 changes: 1 addition & 4 deletions Mail/Views/New Message/ComposeMessageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,7 @@ struct ComposeMessageView: View {
private let htmlAttachments: [HTMLAttachable]

private var isSendButtonDisabled: Bool {
let disabledState = draft.identityId == nil
|| draft.identityId?.isEmpty == true
|| draft.recipientsAreEmpty
let disabledState = draft.recipientsAreEmpty
|| !attachmentsManager.allAttachmentsUploaded
return disabledState
}
Expand Down Expand Up @@ -221,7 +219,6 @@ struct ComposeMessageView: View {

isLoadingContent = false
} catch {
// Unable to get signatures, "An error occurred" and close modal.
snackbarPresenter.show(message: MailError.unknownError.errorDescription ?? "")
dismissMessageView()
}
Expand Down
36 changes: 20 additions & 16 deletions Mail/Views/New Message/Header Cells/ComposeMessageSenderMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import SwiftUI

struct ComposeMessageSenderMenu: View {
@EnvironmentObject private var draftContentManager: DraftContentManager
@EnvironmentObject private var mailboxManager: MailboxManager

@ObservedResults(Signature.self) private var signatures

Expand All @@ -35,7 +36,11 @@ struct ComposeMessageSenderMenu: View {
let text: String

private var canSelectSignature: Bool {
signatures.count > 1
!signatures.isEmpty
}

private var signatureLabel: String {
currentSignature?.formatted(style: canSelectSignature ? .long : .short) ?? mailboxManager.mailbox.emailIdn
}

init(
Expand All @@ -59,23 +64,22 @@ struct ComposeMessageSenderMenu: View {
Text(type.title)
.textStyle(.bodySecondary)

if let currentSignature {
Menu {
ForEach(signatures) { signature in
SenderMenuCell(currentSignature: $currentSignature, signature: signature)
}
} label: {
Text(currentSignature, format: .signature(style: canSelectSignature ? .long : .short))
.textStyle(.body)
.lineLimit(1)
.frame(maxWidth: .infinity, alignment: .leading)

if canSelectSignature {
ChevronIcon(direction: .down)
}
Menu {
SenderMenuCell(currentSignature: $currentSignature, signature: nil)
ForEach(signatures) { signature in
SenderMenuCell(currentSignature: $currentSignature, signature: signature)
}
} label: {
Text(signatureLabel)
.textStyle(.body)
.lineLimit(1)
.frame(maxWidth: .infinity, alignment: .leading)

if canSelectSignature {
ChevronIcon(direction: .down)
}
.disabled(!canSelectSignature)
}
.disabled(!canSelectSignature)
}
.padding(.vertical, UIPadding.composeViewHeaderCellLargeVertical)
.padding(.horizontal, UIPadding.composeViewHeaderHorizontal)
Expand Down
15 changes: 8 additions & 7 deletions Mail/Views/New Message/Header Cells/SenderMenuCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,15 @@ struct SenderMenuCell: View {
@LazyInjectService private var platformDetector: PlatformDetectable

@EnvironmentObject private var draftContentManager: DraftContentManager
@EnvironmentObject private var mailboxManager: MailboxManager

@Binding var currentSignature: Signature?

let signature: Signature
let signature: Signature?

private var signatureLabel: String {
signature?.formatted(style: .option) ?? MailResourcesStrings.Localizable.selectSignatureNone
}

var body: some View {
Button {
Expand All @@ -43,19 +48,15 @@ struct SenderMenuCell: View {
draftContentManager.updateSignature(with: signature)
} label: {
Label {
if platformDetector.isMac {
Text("\(signature.senderName) (\(signature.name)) \(signature.senderEmailIdn)")
} else {
Text("\(signature.senderName) (\(signature.name))")
}
Text(signatureLabel)
} icon: {
if signature == currentSignature {
MailResourcesAsset.check.swiftUIImage
}
}
.accessibilityHint(MailResourcesStrings.Localizable.contentDescriptionButtonSelectSignature)

Text(signature.senderEmailIdn)
Text(signature?.senderEmailIdn ?? mailboxManager.mailbox.emailIdn)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions MailCore/API/Endpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ public extension Endpoint {
return .baseManagerMailbox(hostingId: hostingId, mailboxName: mailboxName).appending(path: "/signatures")
}

static func updateSignature(hostingId: Int, mailboxName: String, signatureId: Int) -> Endpoint {
return .signatures(hostingId: hostingId, mailboxName: mailboxName).appending(path: "/\(signatureId)")
static func updateSignature(hostingId: Int, mailboxName: String) -> Endpoint {
return .signatures(hostingId: hostingId, mailboxName: mailboxName).appending(path: "/set_defaults")
}

static func folders(uuid: String) -> Endpoint {
Expand Down
8 changes: 4 additions & 4 deletions MailCore/API/MailApiFetcher/MailApiFetcher+Extended.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ public extension MailApiFetcher {
authenticatedRequest(
.updateSignature(
hostingId: mailbox.hostingId,
mailboxName: mailbox.mailbox,
signatureId: signature.id
mailboxName: mailbox.mailbox
),
method: .patch,
parameters: signature
method: .post,
parameters: ["default_signature_id": signature.id,
"default_reply_signature_id": nil]
))
}

Expand Down
80 changes: 39 additions & 41 deletions MailCore/Cache/DraftContentManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ extension DraftContentManager {
// MARK: - Write draft

public extension DraftContentManager {
func prepareCompleteDraft() async throws -> Signature {
func prepareCompleteDraft() async throws -> Signature? {
async let draftBodyResult = try await loadCompleteDraftBody()
async let signature = try await loadMostFittingSignature()
async let signature = await loadMostFittingSignature()

try await writeCompleteDraft(
completeBody: draftBodyResult.body,
Expand All @@ -155,7 +155,7 @@ public extension DraftContentManager {
attachments: draftBodyResult.attachments
)

return try await signature
return await signature
}

func replaceContent(subject: String? = nil, body: String) async {
Expand Down Expand Up @@ -184,7 +184,7 @@ public extension DraftContentManager {

private func writeCompleteDraft(
completeBody: String,
signature: Signature,
signature: Signature?,
shouldAddSignatureText: Bool,
attachments: [Attachment]
) throws {
Expand All @@ -195,7 +195,7 @@ public extension DraftContentManager {
}

fetchedDraft = liveIncompleteDraft
if liveIncompleteDraft.identityId == nil || liveIncompleteDraft.identityId?.isEmpty == true {
if let signature, liveIncompleteDraft.identityId == nil || liveIncompleteDraft.identityId?.isEmpty == true {
liveIncompleteDraft.identityId = "\(signature.id)"
if shouldAddSignatureText {
liveIncompleteDraft.rawSignature = signature.content
Expand Down Expand Up @@ -312,25 +312,33 @@ extension DraftContentManager {
// MARK: - Signatures

extension DraftContentManager {
public func updateSignature(with newSignature: Signature) {
public func updateSignature(with newSignature: Signature?) {
do {
let liveIncompleteDraft = try getLiveDraft()

let parsedMessage = try SwiftSoup.parse(liveIncompleteDraft.body)
// If we find the previous signature, we replace it with the new one
// otherwise we append the signature at the end of the document
if let foundSignatureDiv = try parsedMessage.select(".\(Constants.signatureHTMLClass)").first {
try foundSignatureDiv.html(newSignature.content)
} else if let body = parsedMessage.body() {
if let newSignature {
try foundSignatureDiv.html(newSignature.content)
} else {
try foundSignatureDiv.remove()
}
} else if let body = parsedMessage.body(), let newSignature {
let signatureDiv = try body.appendElement("div")
try signatureDiv.addClass(Constants.signatureHTMLClass)
try signatureDiv.html(newSignature.content)
}

try? mailboxManager.writeTransaction { _ in
// Keep up to date the rawSignature
liveIncompleteDraft.rawSignature = newSignature.content
liveIncompleteDraft.identityId = "\(newSignature.id)"
var identityId: String?
if let newSignature {
identityId = "\(newSignature.id)"
}
// Keep up to date the rawSignature
liveIncompleteDraft.rawSignature = newSignature?.content
liveIncompleteDraft.identityId = identityId
liveIncompleteDraft.body = try parsedMessage.outerHtml()
}

Expand All @@ -341,46 +349,36 @@ extension DraftContentManager {
}

/// Load best signature from local DB
private func loadMostFittingSignature() async throws -> Signature {
do {
let storedSignatures = mailboxManager.getStoredSignatures()
let defaultSignature = try getDefaultSignature(userSignatures: storedSignatures)

// If draft already has an identity, return corresponding signature
if let storedDraft = mailboxManager.fetchObject(ofType: Draft.self, forPrimaryKey: incompleteDraft.localUUID),
let identityId = storedDraft.identityId {
return getSignature(for: identityId, userSignatures: storedSignatures) ?? defaultSignature
}

// If draft is a new message or a forward, use default signature
guard let messageReply, messageReply.replyMode == .reply || messageReply.replyMode == .replyAll else {
return defaultSignature
}
private func loadMostFittingSignature() async -> Signature? {
let storedSignatures = mailboxManager.getStoredSignatures()
let defaultSignature = getDefaultSignature(userSignatures: storedSignatures)

// If draft already has an identity, return corresponding signature
if let storedDraft = mailboxManager.fetchObject(ofType: Draft.self, forPrimaryKey: incompleteDraft.localUUID),
let identityId = storedDraft.identityId {
return getSignature(for: identityId, userSignatures: storedSignatures) ?? defaultSignature
}

return guessMostFittingSignature(userSignatures: storedSignatures, defaultSignature: defaultSignature)
} catch {
SentrySDK.capture(message: "We failed to fetch Signatures. This will close the Editor.") { scope in
scope.setExtras([
"errorMessage": error.localizedDescription,
"error": "\(error)"
])
}
throw error
// If draft is a new message or a forward, use default signature
guard let messageReply, messageReply.isReplying else {
return defaultSignature
}

return guessMostFittingSignature(userSignatures: storedSignatures, defaultSignature: defaultSignature)
}

private func getSignature(for identity: String, userSignatures: [Signature]) -> Signature? {
return userSignatures.first { identity == "\($0.id)" }?.freezeIfNeeded()
}

private func getDefaultSignature(userSignatures: [Signature]) throws -> Signature {
guard let defaultSignature = userSignatures.defaultSignature else {
throw MailError.defaultSignatureMissing
}
return defaultSignature.freezeIfNeeded()
private func getDefaultSignature(userSignatures: [Signature]) -> Signature? {
let isReply = messageReply?.isReplying ?? false

let defaultSignature = isReply ? userSignatures.defaultReplySignature : userSignatures.defaultSignature
return defaultSignature?.freezeIfNeeded()
}

private func guessMostFittingSignature(userSignatures: [Signature], defaultSignature: Signature) -> Signature {
private func guessMostFittingSignature(userSignatures: [Signature], defaultSignature: Signature?) -> Signature? {
guard let previousMessage = messageReply?.frozenMessage else { return defaultSignature }

let signaturesGroupedByEmail = Dictionary(grouping: userSignatures, by: \.senderEmail)
Expand Down
2 changes: 1 addition & 1 deletion MailCore/Cache/MailboxManager/MailboxManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public final class MailboxManager: ObservableObject, MailboxManageable {
let realmName = "\(mailbox.userId)-\(mailbox.mailboxId).realm"
realmConfiguration = Realm.Configuration(
fileURL: MailboxManager.constants.rootDocumentsURL.appendingPathComponent(realmName),
schemaVersion: 28,
schemaVersion: 29,
migrationBlock: { migration, oldSchemaVersion in
// No migration needed from 0 to 16
if oldSchemaVersion < 17 {
Expand Down
6 changes: 6 additions & 0 deletions MailCore/Cache/RefreshActor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ public actor RefreshActor {
let signaturesResult = try await mailboxManager.apiFetcher.signatures(mailbox: mailboxManager.mailbox)
var updatedSignatures = Set(signaturesResult.signatures)

if let defaultReplyId = signaturesResult.defaultReplySignatureId {
updatedSignatures.first {
$0.id == defaultReplyId
}?.isDefaultReply = true
}

await mailboxManager.backgroundRealm.execute { realm in
let signaturesToDelete: Set<Signature> // no longer present server side
let signaturesToUpdate: [Signature] // updated signatures
Expand Down
22 changes: 13 additions & 9 deletions MailCore/Models/Signature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,33 @@ public enum SignaturePosition: String, PersistableEnum, Codable {

public struct SignatureResponse: Decodable {
public var signatures: [Signature]
public var defaultSignatureId: Int?
public var defaultReplySignatureId: Int?

public var `default`: Signature? {
signatures.first(where: \.isDefault)
}

private enum CodingKeys: String, CodingKey {
case signatures
case defaultSignatureId
case defaultReplySignatureId
}
}

public final class Signature: Object, Codable, Identifiable {
@Persisted(primaryKey: true) public var id: Int
@Persisted public var name: String
@Persisted public var content: String
@Persisted public var replyToId: Int
@Persisted public var senderName: String
@Persisted public var senderEmail: String
@Persisted public var senderEmailIdn: String
@Persisted public var senderId: Int
@Persisted public var isDefault: Bool
@Persisted public var isDefaultReply = false
@Persisted public var position: SignaturePosition

private enum CodingKeys: String, CodingKey {
case id, name, content, replyToId, senderName = "fullName", senderEmail = "sender", senderEmailIdn = "senderIdn",
senderId, isDefault, position
case id, name, content, senderName = "fullName", senderEmail = "sender", senderEmailIdn = "senderIdn", isDefault, position
}

override public var hash: Int {
Expand Down Expand Up @@ -86,12 +88,14 @@ public extension Signature {
public extension [Signature] {
/// Find the default signature, if any, in an `Array` of `Signature`
var defaultSignature: Signature? {
guard let defaultSignature = first(where: \.isDefault) else {
// We try to return at least a signature, so the backend is happy. Same on Android.
return first
return first(where: \.isDefault)
}

var defaultReplySignature: Signature? {
guard let defaultReplySignature = first(where: \.isDefaultReply) else {
return defaultSignature
}

// We matched one
return defaultSignature
return defaultReplySignature
}
}
Loading

0 comments on commit 330e7be

Please sign in to comment.