diff --git a/.package.resolved b/.package.resolved
index db9b21a4c..728e82ed5 100644
--- a/.package.resolved
+++ b/.package.resolved
@@ -41,7 +41,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Infomaniak/ios-core",
"state" : {
- "revision" : "e5fe8e03aa06375f20c8e40f4fae3a6af6e4d75e"
+ "revision" : "ef2811a288a3a4b94dd5d04c5f47ddd771e4176c"
}
},
{
@@ -139,8 +139,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Nuke",
"state" : {
- "revision" : "282b9562c84d76cebbbecd1cc3aaf430448a0cce",
- "version" : "12.1.1"
+ "revision" : "f67266f176af4add9f7a7020486826d82d562473",
+ "version" : "12.1.2"
}
},
{
@@ -157,8 +157,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/realm/realm-core.git",
"state" : {
- "revision" : "93cc224680a520a84e1c01aa8866042c52eb7958",
- "version" : "13.15.0"
+ "revision" : "f1434caadda443b4ed2261b91ea4f43ab1ee2aa5",
+ "version" : "13.15.1"
}
},
{
@@ -166,8 +166,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/realm/realm-swift",
"state" : {
- "revision" : "2d7d1463a765c8835262dc09da83906e7cea166c",
- "version" : "10.40.2"
+ "revision" : "b287dc102036ff425bd8a88483f0a5596871f05e",
+ "version" : "10.41.0"
}
},
{
@@ -237,8 +237,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/scinfu/SwiftSoup",
"state" : {
- "revision" : "0e96a20ffd37a515c5c963952d4335c89bed50a6",
- "version" : "2.6.0"
+ "revision" : "8b6cf29eead8841a1fa7822481cb3af4ddaadba6",
+ "version" : "2.6.1"
}
},
{
@@ -246,8 +246,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/siteline/SwiftUI-Introspect",
"state" : {
- "revision" : "dafd88cf0cc09d906dcef9d042743ae13fcff0d4",
- "version" : "0.6.3"
+ "revision" : "8bf15ad33a529359200bd419a72ca2dda841089b",
+ "version" : "0.8.0"
}
},
{
diff --git a/Mail/Components/MailboxListView.swift b/Mail/Components/MailboxListView.swift
new file mode 100644
index 000000000..81dc8b4b4
--- /dev/null
+++ b/Mail/Components/MailboxListView.swift
@@ -0,0 +1,74 @@
+/*
+ Infomaniak Mail - iOS App
+ Copyright (C) 2022 Infomaniak Network SA
+
+ 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 .
+ */
+
+import MailCore
+import MailResources
+import RealmSwift
+import SwiftUI
+
+struct MailboxListView: View {
+ @Environment(\.window) private var window
+
+ @AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor
+
+ @ObservedResults(
+ Mailbox.self,
+ configuration: MailboxInfosManager.instance.realmConfiguration,
+ where: { $0.userId == AccountManager.instance.currentUserId },
+ sortDescriptor: SortDescriptor(keyPath: \Mailbox.mailboxId)
+ ) private var mailboxes
+
+ private var otherMailboxes: [Mailbox] {
+ return mailboxes.filter { $0.mailboxId != currentMailbox?.mailboxId }
+ }
+
+ let currentMailbox: Mailbox?
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 12) {
+ HStack(alignment: .center) {
+ Text(MailResourcesStrings.Localizable.buttonAccountAssociatedEmailAddresses)
+ .textStyle(.bodySmallSecondary)
+
+ Spacer()
+
+ NavigationLink {
+ AddMailboxView()
+ } label: {
+ MailResourcesAsset.addCircle.swiftUIImage
+ .resizable()
+ .foregroundColor(accentColor.primary)
+ .frame(width: 16, height: 16)
+ }
+ }
+ .padding(.bottom, 16)
+
+ if let currentMailbox {
+ MailboxCell(mailbox: currentMailbox, isSelected: true)
+ .mailboxCellStyle(.account)
+ }
+
+ ForEach(otherMailboxes) { mailbox in
+ MailboxCell(mailbox: mailbox)
+ .mailboxCellStyle(.account)
+ }
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding(.top, 24)
+ }
+}
diff --git a/Mail/Components/UnavailableMailboxListView.swift b/Mail/Components/UnavailableMailboxListView.swift
new file mode 100644
index 000000000..9b73df307
--- /dev/null
+++ b/Mail/Components/UnavailableMailboxListView.swift
@@ -0,0 +1,78 @@
+/*
+ Infomaniak Mail - iOS App
+ Copyright (C) 2022 Infomaniak Network SA
+
+ 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 .
+ */
+
+import MailCore
+import MailResources
+import RealmSwift
+import SwiftUI
+
+struct UnavailableMailboxListView: View {
+ @Environment(\.window) private var window
+
+ @AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor
+
+ @ObservedResults(
+ Mailbox.self,
+ configuration: MailboxInfosManager.instance.realmConfiguration,
+ where: { $0.userId == AccountManager.instance.currentUserId && $0.isPasswordValid == false },
+ sortDescriptor: SortDescriptor(keyPath: \Mailbox.mailboxId)
+ ) private var passwordBlockedMailboxes
+
+ @ObservedResults(
+ Mailbox.self,
+ configuration: MailboxInfosManager.instance.realmConfiguration,
+ where: { $0.userId == AccountManager.instance.currentUserId && $0.isLocked == true },
+ sortDescriptor: SortDescriptor(keyPath: \Mailbox.mailboxId)
+ ) private var lockedMailboxes
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 32) {
+ if !passwordBlockedMailboxes.isEmpty {
+ VStack(alignment: .leading, spacing: 12) {
+ Text(MailResourcesStrings.Localizable.blockedPasswordTitlePlural)
+ ForEach(passwordBlockedMailboxes) { mailbox in
+ MailboxCell(mailbox: mailbox)
+ .mailboxCellStyle(.setPassword)
+ }
+ }
+ }
+
+ if !lockedMailboxes.isEmpty {
+ VStack(alignment: .leading, spacing: 12) {
+ Text(MailResourcesStrings.Localizable.lockedMailboxTitlePlural)
+ ForEach(lockedMailboxes) { mailbox in
+ MailboxesManagementButtonView(
+ icon: MailResourcesAsset.envelope,
+ text: mailbox.email,
+ isSelected: false,
+ isInMaintenance: false
+ )
+ }
+ }
+ }
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding(.top, 24)
+ }
+}
+
+struct UnavailableMailboxListView_Previews: PreviewProvider {
+ static var previews: some View {
+ UnavailableMailboxListView()
+ }
+}
diff --git a/Mail/Components/UnreadIndicatorView.swift b/Mail/Components/UnreadIndicatorView.swift
index fc5087a89..05956d882 100644
--- a/Mail/Components/UnreadIndicatorView.swift
+++ b/Mail/Components/UnreadIndicatorView.swift
@@ -20,7 +20,8 @@ import MailCore
import SwiftUI
struct UnreadIndicatorView: View {
- let hidden: Bool
+ var hidden = false
+
var body: some View {
Circle()
.frame(width: UIConstants.unreadIconSize, height: UIConstants.unreadIconSize)
diff --git a/Mail/Helpers/PreviewHelper.swift b/Mail/Helpers/PreviewHelper.swift
index fac270a20..d8572f987 100644
--- a/Mail/Helpers/PreviewHelper.swift
+++ b/Mail/Helpers/PreviewHelper.swift
@@ -55,23 +55,17 @@ enum PreviewHelper {
children: [])
static let sampleThread = Thread(uid: "",
- messagesCount: 2,
- deletedMessagesCount: 0,
messages: [sampleMessage],
unseenMessages: 1,
from: [sampleRecipient1],
to: [sampleRecipient2],
- cc: [],
- bcc: [],
subject: "Test thread",
date: SentryDebug.knownDebugDate,
hasAttachments: true,
- hasSwissTransferAttachments: false,
hasDrafts: false,
flagged: true,
answered: true,
- forwarded: true,
- size: 0)
+ forwarded: true)
static let sampleMessage = Message(uid: "",
msgId: "",
diff --git a/Mail/Views/Menu Drawer/Folders/FolderCell.swift b/Mail/Views/Menu Drawer/Folders/FolderCell.swift
index 75baae4b5..c370469ae 100644
--- a/Mail/Views/Menu Drawer/Folders/FolderCell.swift
+++ b/Mail/Views/Menu Drawer/Folders/FolderCell.swift
@@ -158,9 +158,14 @@ struct FolderCellContent: View {
@ViewBuilder
private var accessory: some View {
if cellType == .link {
- if folder.role != .sent {
- Text(folder.formattedUnreadCount)
- .textStyle(.bodySmallMediumAccent)
+ if folder.role != .sent && folder.role != .trash {
+ if !folder.formattedUnreadCount.isEmpty {
+ Text(folder.formattedUnreadCount)
+ .textStyle(.bodySmallMediumAccent)
+ } else if folder.remoteUnreadCount > 0 {
+ UnreadIndicatorView()
+ .accessibilityLabel(MailResourcesStrings.Localizable.contentDescriptionUnreadPastille)
+ }
}
} else if isCurrentFolder {
MailResourcesAsset.check.swiftUIImage
diff --git a/Mail/Views/Menu Drawer/MailboxManagement/MailboxCell.swift b/Mail/Views/Menu Drawer/MailboxManagement/MailboxCell.swift
index a39273fb7..01e40b090 100644
--- a/Mail/Views/Menu Drawer/MailboxManagement/MailboxCell.swift
+++ b/Mail/Views/Menu Drawer/MailboxManagement/MailboxCell.swift
@@ -44,18 +44,18 @@ struct MailboxCell: View {
@Environment(\.mailboxCellStyle) private var style: Style
@EnvironmentObject private var navigationDrawerState: NavigationDrawerState
- let mailbox: Mailbox
+ @State private var isShowingLockedView = false
+ @State private var isShowingUpdatePasswordView = false
- private var isSelected: Bool {
- return AccountManager.instance.currentMailboxManager?.mailbox.objectId == mailbox.objectId
- }
+ let mailbox: Mailbox
+ var isSelected = false
private var detailNumber: Int? {
return mailbox.unseenMessages > 0 ? mailbox.unseenMessages : nil
}
enum Style {
- case menuDrawer, account
+ case menuDrawer, account, setPassword
}
var body: some View {
@@ -64,15 +64,20 @@ struct MailboxCell: View {
text: mailbox.email,
detailNumber: detailNumber,
isSelected: isSelected,
- isPasswordValid: mailbox.isPasswordValid
+ isInMaintenance: !mailbox.isAvailable
) {
guard !isSelected else { return }
guard mailbox.isPasswordValid else {
- IKSnackBar.showSnackBar(message: MailResourcesStrings.Localizable.frelatedMailbox)
+ isShowingUpdatePasswordView = true
+ return
+ }
+ guard !mailbox.isLocked else {
+ isShowingLockedView = true
return
}
@InjectService var matomo: MatomoUtils
switch style {
+ case .setPassword: break
case .menuDrawer:
matomo.track(eventWithCategory: .menuDrawer, name: "switchMailbox")
case .account:
@@ -81,11 +86,17 @@ struct MailboxCell: View {
AccountManager.instance.switchMailbox(newMailbox: mailbox)
navigationDrawerState.close()
}
+ .floatingPanel(isPresented: $isShowingLockedView) {
+ LockedMailboxView(lockedMailbox: mailbox)
+ }
+ .sheet(isPresented: $isShowingUpdatePasswordView) {
+ UpdateMailboxPasswordView(mailbox: mailbox)
+ }
}
}
struct MailboxCell_Previews: PreviewProvider {
static var previews: some View {
- MailboxCell(mailbox: PreviewHelper.sampleMailbox)
+ MailboxCell(mailbox: PreviewHelper.sampleMailbox, isSelected: true)
}
}
diff --git a/Mail/Views/Menu Drawer/MailboxManagement/MailboxesManagementButtonView.swift b/Mail/Views/Menu Drawer/MailboxManagement/MailboxesManagementButtonView.swift
index b39ba8727..2eefafec0 100644
--- a/Mail/Views/Menu Drawer/MailboxManagement/MailboxesManagementButtonView.swift
+++ b/Mail/Views/Menu Drawer/MailboxManagement/MailboxesManagementButtonView.swift
@@ -27,28 +27,30 @@ struct MailboxesManagementButtonView: View {
let icon: Image
let text: String
let detailNumber: Int?
- let handleAction: () -> Void
+ let handleAction: (() -> Void)?
let isSelected: Bool
- let isPasswordValid: Bool
+ let isInMaintenance: Bool
init(
icon: MailResourcesImages,
text: String,
detailNumber: Int? = nil,
isSelected: Bool,
- isPasswordValid: Bool,
- handleAction: @escaping () -> Void
+ isInMaintenance: Bool,
+ handleAction: (() -> Void)? = nil
) {
self.icon = icon.swiftUIImage
self.text = text
self.detailNumber = detailNumber
self.isSelected = isSelected
- self.isPasswordValid = isPasswordValid
+ self.isInMaintenance = isInMaintenance
self.handleAction = handleAction
}
var body: some View {
- Button(action: handleAction) {
+ Button {
+ handleAction?()
+ } label: {
HStack {
HStack(spacing: 16) {
icon
@@ -62,10 +64,15 @@ struct MailboxesManagementButtonView: View {
}
.frame(maxWidth: .infinity, alignment: .leading)
- if !isPasswordValid {
+ if isInMaintenance && style != .setPassword {
MailResourcesAsset.warning.swiftUIImage
} else {
switch style {
+ case .setPassword:
+ MailResourcesAsset.arrowRight.swiftUIImage
+ .resizable()
+ .frame(width: 12, height: 12)
+ .foregroundColor(MailResourcesAsset.textPrimaryColor.swiftUIColor)
case .menuDrawer:
if let detailNumber {
Text(detailNumber < 100 ? "\(detailNumber)" : "99+")
@@ -87,17 +94,13 @@ struct MailboxesManagementButtonView: View {
struct MailboxesManagementButtonView_Previews: PreviewProvider {
static var previews: some View {
- MailboxesManagementButtonView(icon: MailResourcesAsset.folder, text: "Hello", isSelected: false, isPasswordValid: true) {
- /* Empty for test */
- }
+ MailboxesManagementButtonView(icon: MailResourcesAsset.folder, text: "Hello", isSelected: false, isInMaintenance: true)
MailboxesManagementButtonView(
icon: MailResourcesAsset.folder,
text: "Hello",
detailNumber: 10,
isSelected: false,
- isPasswordValid: true
- ) {
- /* Empty for test */
- }
+ isInMaintenance: true
+ )
}
}
diff --git a/Mail/Views/New Message/Attachments/AttachmentsHeaderView.swift b/Mail/Views/New Message/Attachments/AttachmentsHeaderView.swift
index 78914808d..d5eb8f4e3 100644
--- a/Mail/Views/New Message/Attachments/AttachmentsHeaderView.swift
+++ b/Mail/Views/New Message/Attachments/AttachmentsHeaderView.swift
@@ -31,7 +31,7 @@ struct AttachmentsHeaderView: View {
HStack(spacing: 8) {
ForEach(attachmentsManager.attachments) { attachment in
AttachmentUploadCell(attachment: attachment,
- uploadTask: attachmentsManager.attachmentUploadTaskFor(uuid: attachment.uuid)) { attachmentRemoved in
+ uploadTask: attachmentsManager.attachmentUploadTaskOrFinishedTask(for: attachment.uuid)) { attachmentRemoved in
attachmentsManager.removeAttachment(attachmentRemoved)
}
}
diff --git a/Mail/Views/New Message/Attachments/AttachmentsManager.swift b/Mail/Views/New Message/Attachments/AttachmentsManager.swift
index ca89aa2ee..ab91efc8b 100644
--- a/Mail/Views/New Message/Attachments/AttachmentsManager.swift
+++ b/Mail/Views/New Message/Attachments/AttachmentsManager.swift
@@ -59,7 +59,7 @@ class AttachmentsManager: ObservableObject {
func completeUploadedAttachments() {
for attachment in attachments {
- let uploadTask = attachmentUploadTaskFor(uuid: attachment.uuid)
+ let uploadTask = attachmentUploadTaskOrCreate(for: attachment.uuid)
uploadTask.progress = 1
}
objectWillChange.send()
@@ -84,11 +84,36 @@ class AttachmentsManager: ObservableObject {
objectWillChange.send()
}
- func attachmentUploadTaskFor(uuid: String) -> AttachmentUploadTask {
- if attachmentUploadTasks[uuid] == nil {
- attachmentUploadTasks[uuid] = AttachmentUploadTask()
+ /// Lookup and return. New object created and returned instead
+ func attachmentUploadTaskOrCreate(for uuid: String) -> AttachmentUploadTask {
+ guard let attachment = attachmentUploadTask(for: uuid) else {
+ let newTask = AttachmentUploadTask()
+ attachmentUploadTasks[uuid] = newTask
+ return newTask
}
- return attachmentUploadTasks[uuid]!
+
+ return attachment
+ }
+
+ /// Lookup and return. New object representing a finished task instead.
+ func attachmentUploadTaskOrFinishedTask(for uuid: String) -> AttachmentUploadTask {
+ guard let attachment = attachmentUploadTask(for: uuid) else {
+ let finishedTask = AttachmentUploadTask()
+ finishedTask.progress = 1
+ attachmentUploadTasks[uuid] = finishedTask
+ return finishedTask
+ }
+
+ return attachment
+ }
+
+ /// Lookup and return, nil if not found
+ private func attachmentUploadTask(for uuid: String) -> AttachmentUploadTask? {
+ guard let attachment = attachmentUploadTasks[uuid] else {
+ return nil
+ }
+
+ return attachment
}
func removeAttachment(_ attachment: Attachment) {
diff --git a/Mail/Views/New Message/ComposeMessageView.swift b/Mail/Views/New Message/ComposeMessageView.swift
index 69a910fac..3c3d34423 100644
--- a/Mail/Views/New Message/ComposeMessageView.swift
+++ b/Mail/Views/New Message/ComposeMessageView.swift
@@ -63,14 +63,11 @@ struct ComposeMessageView: View {
@LazyInjectService private var matomo: MatomoUtils
@LazyInjectService private var draftManager: DraftManager
- @State private var isLoadingContent: Bool
+ @State private var isLoadingContent = true
@State private var isShowingCancelAttachmentsError = false
@State private var autocompletionType: ComposeViewFieldType?
@State private var editorFocus = false
- /// Something to track the initial loading of a default signature
- @StateObject private var signatureManager: SignaturesManager
- @StateObject private var mailboxManager: MailboxManager
@StateObject private var attachmentsManager: AttachmentsManager
@StateObject private var alert = NewMessageAlert()
@@ -83,7 +80,9 @@ struct ComposeMessageView: View {
}
}
- let messageReply: MessageReply?
+ private let messageReply: MessageReply?
+ private let draftContentManager: DraftContentManager
+ private let mailboxManager: MailboxManager
private var isSendButtonDisabled: Bool {
let disabledState = draft.identityId == nil
@@ -93,7 +92,7 @@ struct ComposeMessageView: View {
return disabledState
}
- // MAK: - Int
+ // MARK: - Init
init(draft: Draft, mailboxManager: MailboxManager, messageReply: MessageReply? = nil) {
self.messageReply = messageReply
@@ -101,14 +100,17 @@ struct ComposeMessageView: View {
Self.saveNewDraftInRealm(mailboxManager.getRealm(), draft: draft)
_draft = StateRealmObject(wrappedValue: draft)
- _isLoadingContent = State(wrappedValue: (draft.messageUid != nil && draft.remoteUUID.isEmpty) || messageReply != nil)
+ draftContentManager = DraftContentManager(
+ incompleteDraft: draft,
+ messageReply: messageReply,
+ mailboxManager: mailboxManager
+ )
- _signatureManager = StateObject(wrappedValue: SignaturesManager(mailboxManager: mailboxManager))
- _mailboxManager = StateObject(wrappedValue: mailboxManager)
+ self.mailboxManager = mailboxManager
_attachmentsManager = StateObject(wrappedValue: AttachmentsManager(draft: draft, mailboxManager: mailboxManager))
}
- // MAK: - View
+ // MARK: - View
var body: some View {
NavigationView {
@@ -139,7 +141,7 @@ struct ComposeMessageView: View {
VStack(spacing: 0) {
ComposeMessageHeaderView(draft: draft, focusedField: _focusedField, autocompletionType: $autocompletionType)
- if autocompletionType == nil {
+ if autocompletionType == nil && !isLoadingContent {
ComposeMessageBodyView(
draft: draft,
isLoadingContent: $isLoadingContent,
@@ -153,21 +155,15 @@ struct ComposeMessageView: View {
}
}
.task {
- await prepareCompleteDraft()
- }
- .task {
- await prepareReplyForwardBodyAndAttachments()
- }
- .onChange(of: signatureManager.loadingSignatureState) { state in
- switch state {
- case .success:
- setSignature()
- case .error:
+ do {
+ isLoadingContent = true
+ try await draftContentManager.prepareCompleteDraft()
+ attachmentsManager.completeUploadedAttachments()
+ isLoadingContent = false
+ } catch {
// Unable to get signatures, "An error occurred" and close modal.
IKSnackBar.showSnackBar(message: MailError.unknownError.localizedDescription)
dismiss()
- case .progress:
- break
}
}
.background(MailResourcesAsset.backgroundColor.swiftUIColor)
@@ -183,7 +179,7 @@ struct ComposeMessageView: View {
draftManager.syncDraft(mailboxManager: mailboxManager)
}
.overlay {
- if isLoadingContent || signatureManager.loadingSignatureState == .progress {
+ if isLoadingContent {
progressView
}
}
@@ -252,76 +248,6 @@ struct ComposeMessageView: View {
realm.add(draft, update: .modified)
}
}
-
- private func prepareCompleteDraft() async {
- guard draft.messageUid != nil && draft.remoteUUID.isEmpty else { return }
-
- do {
- try await mailboxManager.draft(partialDraft: draft)
- isLoadingContent = false
- } catch {
- dismiss()
- IKSnackBar.showSnackBar(message: MailError.unknownError.localizedDescription)
- }
- }
-
- private func prepareReplyForwardBodyAndAttachments() async {
- guard let messageReply else { return }
-
- let prepareTask = Task.detached {
- try await prepareBody(message: messageReply.message, replyMode: messageReply.replyMode)
- try await prepareAttachments(message: messageReply.message, replyMode: messageReply.replyMode)
- }
-
- do {
- _ = try await prepareTask.value
-
- isLoadingContent = false
- } catch {
- dismiss()
- IKSnackBar.showSnackBar(message: MailError.unknownError.localizedDescription)
- }
- }
-
- private func prepareBody(message: Message, replyMode: ReplyMode) async throws {
- if !message.fullyDownloaded {
- try await mailboxManager.message(message: message)
- }
-
- guard let freshMessage = message.thaw() else { return }
- freshMessage.realm?.refresh()
- $draft.body.wrappedValue = Draft.replyingBody(message: freshMessage, replyMode: replyMode)
- }
-
- private func prepareAttachments(message: Message, replyMode: ReplyMode) async throws {
- guard replyMode == .forward else { return }
- let attachments = try await mailboxManager.apiFetcher.attachmentsToForward(
- mailbox: mailboxManager.mailbox,
- message: message
- ).attachments
-
- for attachment in attachments {
- $draft.attachments.append(attachment)
- }
- attachmentsManager.completeUploadedAttachments()
- }
-
- private func setSignature() {
- guard draft.identityId == nil || draft.identityId?.isEmpty == true else {
- return
- }
-
- guard let defaultSignature = mailboxManager.getStoredSignatures().defaultSignature else {
- return
- }
-
- let body = $draft.body.wrappedValue
- let signedBody = defaultSignature.appendSignature(to: body)
-
- // At this point we have signatures in base up to date, we use the default one.
- $draft.identityId.wrappedValue = "\(defaultSignature.id)"
- $draft.body.wrappedValue = signedBody
- }
}
struct ComposeMessageView_Previews: PreviewProvider {
diff --git a/Mail/Views/New Message/Signatures/SignaturesManager.swift b/Mail/Views/New Message/Signatures/SignaturesManager.swift
deleted file mode 100644
index 22bafb0c3..000000000
--- a/Mail/Views/New Message/Signatures/SignaturesManager.swift
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- Infomaniak Mail - iOS App
- Copyright (C) 2022 Infomaniak Network SA
-
- 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 .
- */
-
-import MailCore
-import Sentry
-import SwiftUI
-
-final class SignaturesManager: ObservableObject {
- /// Represents the loading state
- enum SignaturesLoadingState: Equatable {
- static func == (lhs: SignaturesManager.SignaturesLoadingState, rhs: SignaturesManager.SignaturesLoadingState) -> Bool {
- switch (lhs, rhs) {
- case (.success, .success):
- return true
- case (.progress, .progress):
- return true
- case (.error(let left), .error(let right)):
- return left == right
- default:
- return false
- }
- }
-
- case success
- case progress
- case error(_ wrapping: NSError)
- }
-
- @Published var loadingSignatureState: SignaturesLoadingState = .progress
-
- private let mailboxManager: MailboxManager
- init(mailboxManager: MailboxManager) {
- self.mailboxManager = mailboxManager
-
- loadRemoteSignatures()
- }
-
- /// Load the signatures every time at init, set `doneLoadingDefaultSignature` to true when done
- private func loadRemoteSignatures() {
- Task {
- do {
- // load all signatures every time
- try await mailboxManager.refreshAllSignatures()
-
- // If after a refresh we have no default signature we bail
- guard mailboxManager.getStoredSignatures().defaultSignature != nil else {
- throw MailError.defaultSignatureMissing
- }
-
- await MainActor.run {
- loadingSignatureState = .success
- }
- } catch {
- await MainActor.run {
- loadingSignatureState = .error(error as NSError)
- }
-
- SentrySDK.capture(message: "We failed to fetch Signatures. This will close the Editor.") { scope in
- scope.setExtras([
- "errorMessage": error.localizedDescription,
- "error": "\(error)"
- ])
- }
- }
- }
- }
-}
diff --git a/Mail/Views/NoMailboxView.swift b/Mail/Views/NoMailboxView.swift
index 54b806c8d..5bd9d9455 100644
--- a/Mail/Views/NoMailboxView.swift
+++ b/Mail/Views/NoMailboxView.swift
@@ -56,6 +56,9 @@ struct NoMailboxView: View {
.frame(height: UIConstants.onboardingButtonHeight + UIConstants.onboardingBottomButtonPadding, alignment: .top)
.padding(.horizontal, 24)
}
+ .sheet(isPresented: $isShowingAddMailboxView) {
+ AddMailboxView()
+ }
.matomoView(view: ["NoMailboxView"])
.sheet(isPresented: $isShowingAddMailboxView) {
AddMailboxView()
diff --git a/Mail/Views/Settings/General/SettingsNotificationsView.swift b/Mail/Views/Settings/General/SettingsNotificationsView.swift
index 6b364d3b0..f9260d2ab 100644
--- a/Mail/Views/Settings/General/SettingsNotificationsView.swift
+++ b/Mail/Views/Settings/General/SettingsNotificationsView.swift
@@ -28,6 +28,8 @@ struct SettingsNotificationsView: View {
@LazyInjectService private var notificationService: InfomaniakNotifications
@LazyInjectService private var matomo: MatomoUtils
+ @EnvironmentObject var mailboxManager: MailboxManager
+
@AppStorage(UserDefaults.shared.key(.notificationsEnabled)) private var notificationsEnabled = DefaultPreferences
.notificationsEnabled
@State var subscribedTopics: [String]?
@@ -132,7 +134,7 @@ struct SettingsNotificationsView: View {
}
func currentTopics() async {
- let currentSubscription = await notificationService.subscriptionForUser(id: AccountManager.instance.currentUserId)
+ let currentSubscription = await notificationService.subscriptionForUser(id: mailboxManager.mailbox.userId)
withAnimation {
self.subscribedTopics = currentSubscription?.topics
}
@@ -140,9 +142,8 @@ struct SettingsNotificationsView: View {
func updateTopicsForCurrentUserIfNeeded() {
Task {
- guard let currentApiFetcher = AccountManager.instance.currentMailboxManager?.apiFetcher,
- let subscribedTopics else { return }
- await notificationService.updateTopicsIfNeeded(subscribedTopics, userApiFetcher: currentApiFetcher)
+ guard let subscribedTopics else { return }
+ await notificationService.updateTopicsIfNeeded(subscribedTopics, userApiFetcher: mailboxManager.apiFetcher)
}
}
diff --git a/Mail/Views/Switch User/AccountCellView.swift b/Mail/Views/Switch User/AccountCellView.swift
index d84388268..6ef9e0dfe 100644
--- a/Mail/Views/Switch User/AccountCellView.swift
+++ b/Mail/Views/Switch User/AccountCellView.swift
@@ -45,6 +45,8 @@ struct AccountCellView: View {
VStack {
Button {
+ guard !isSelected else { return }
+
@InjectService var matomo: MatomoUtils
matomo.track(eventWithCategory: .account, name: "switch")
dismissModal()
diff --git a/Mail/Views/Switch User/AccountView.swift b/Mail/Views/Switch User/AccountView.swift
index e662881a4..14c0cd0b4 100644
--- a/Mail/Views/Switch User/AccountView.swift
+++ b/Mail/Views/Switch User/AccountView.swift
@@ -43,6 +43,9 @@ class AccountViewDelegate: DeleteAccountDelegate {
}
struct AccountView: View {
+ @Environment(\.dismiss) private var dismiss
+
+ @EnvironmentObject private var mailboxManager: MailboxManager
@AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor
@LazyInjectService private var matomo: MatomoUtils
@@ -52,13 +55,6 @@ struct AccountView: View {
@State private var isShowingDeleteAccount = false
@State private var delegate = AccountViewDelegate()
- @State var mailboxes: [Mailbox]
-
- let selectedMailbox = AccountManager.instance.currentMailboxManager?.mailbox
- var otherMailbox: [Mailbox] {
- return mailboxes.filter { $0.mailboxId != selectedMailbox?.mailboxId }
- }
-
var body: some View {
VStack(spacing: 0) {
ScrollView {
@@ -102,15 +98,7 @@ struct AccountView: View {
}
.padding(.bottom, 16)
- if let currentMailbox = selectedMailbox {
- MailboxCell(mailbox: currentMailbox)
- .mailboxCellStyle(.account)
- }
-
- ForEach(otherMailbox) { mailbox in
- MailboxCell(mailbox: mailbox)
- .mailboxCellStyle(.account)
- }
+ MailboxListView(currentMailbox: mailboxManager.mailbox)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.top, 24)
@@ -148,6 +136,6 @@ struct AccountView: View {
struct AccountView_Previews: PreviewProvider {
static var previews: some View {
- AccountView(mailboxes: [PreviewHelper.sampleMailbox])
+ AccountView()
}
}
diff --git a/Mail/Views/Thread List/ThreadListModifiers.swift b/Mail/Views/Thread List/ThreadListModifiers.swift
index e58275987..71e0711a5 100644
--- a/Mail/Views/Thread List/ThreadListModifiers.swift
+++ b/Mail/Views/Thread List/ThreadListModifiers.swift
@@ -128,7 +128,7 @@ struct ThreadListToolbar: ViewModifier {
}
.accessibilityLabel(MailResourcesStrings.Localizable.contentDescriptionUserAvatar)
.sheet(isPresented: $isShowingSwitchAccount) {
- AccountView(mailboxes: AccountManager.instance.mailboxes)
+ AccountView()
}
}
}
diff --git a/Mail/Views/Thread/MessageHeaderView.swift b/Mail/Views/Thread/MessageHeaderView.swift
index e1d2dc553..5f9ae03f5 100644
--- a/Mail/Views/Thread/MessageHeaderView.swift
+++ b/Mail/Views/Thread/MessageHeaderView.swift
@@ -52,7 +52,7 @@ struct MessageHeaderView: View {
.onTapGesture {
if message.isDraft {
DraftUtils.editDraft(from: message, mailboxManager: mailboxManager, editedMessageDraft: $editedDraft)
- } else if message.originalThread?.messagesCount ?? 0 > 1 {
+ } else if message.originalThread?.messages.isEmpty == false {
withAnimation {
isHeaderExpanded = false
isMessageExpanded.toggle()
diff --git a/Mail/Views/Thread/MessageView.swift b/Mail/Views/Thread/MessageView.swift
index 756b5680e..2e70149b1 100644
--- a/Mail/Views/Thread/MessageView.swift
+++ b/Mail/Views/Thread/MessageView.swift
@@ -18,8 +18,6 @@
import CocoaLumberjackSwift
import InfomaniakCore
-import InfomaniakCoreUI
-import InfomaniakDI
import MailCore
import MailResources
import RealmSwift
@@ -27,8 +25,6 @@ import SwiftUI
/// Something that can display an email
struct MessageView: View {
- @LazyInjectService var matomo: MatomoUtils
-
@EnvironmentObject var mailboxManager: MailboxManager
@State var presentableBody: PresentableBody
diff --git a/Mail/Views/Thread/ThreadView.swift b/Mail/Views/Thread/ThreadView.swift
index 806230e07..4e01acd83 100644
--- a/Mail/Views/Thread/ThreadView.swift
+++ b/Mail/Views/Thread/ThreadView.swift
@@ -78,6 +78,14 @@ struct ThreadView: View {
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { offset in
displayNavigationTitle = offset.y < -85
}
+ .onAppear {
+ matomo.track(
+ eventWithCategory: .userInfo,
+ action: .data,
+ name: "nbMessagesInThread",
+ value: Float(thread.messages.count)
+ )
+ }
.task {
if thread.hasUnseenMessages {
try? await mailboxManager.toggleRead(threads: [thread])
diff --git a/Mail/Views/Thread/WebView.swift b/Mail/Views/Thread/WebView.swift
index f0c4ec707..49eca084a 100644
--- a/Mail/Views/Thread/WebView.swift
+++ b/Mail/Views/Thread/WebView.swift
@@ -29,7 +29,7 @@ enum JavaScriptDeclaration {
var description: String {
switch self {
case .normalizeMessageWidth(let width, let messageUid):
- return "normalizeMessageWidth(\(width), \(messageUid))"
+ return "normalizeMessageWidth(\(width), '\(messageUid)')"
case .removeAllProperties:
return "removeAllProperties()"
case .documentReadyState:
diff --git a/Mail/Views/Unavailable Mailbox/LockedMailboxView.swift b/Mail/Views/Unavailable Mailbox/LockedMailboxView.swift
new file mode 100644
index 000000000..74f3785c5
--- /dev/null
+++ b/Mail/Views/Unavailable Mailbox/LockedMailboxView.swift
@@ -0,0 +1,60 @@
+/*
+ Infomaniak Mail - iOS App
+ Copyright (C) 2022 Infomaniak Network SA
+
+ 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 .
+ */
+
+import MailCore
+import MailResources
+import SwiftUI
+
+struct LockedMailboxView: View {
+ @Environment(\.dismiss) private var dismiss
+
+ let lockedMailbox: Mailbox
+ var body: some View {
+ VStack(spacing: 16) {
+ MailResourcesAsset.mailboxError.swiftUIImage
+ .resizable()
+ .scaledToFit()
+ .frame(height: 64)
+ Text(MailResourcesStrings.Localizable.lockedMailboxTitle)
+ .textStyle(.header2)
+ .multilineTextAlignment(.center)
+ Text(MailResourcesStrings.Localizable.lockedMailboxDescription)
+ .textStyle(.bodySecondary)
+ .multilineTextAlignment(.center)
+ .padding(.vertical, 24)
+
+ MailButton(label: MailResourcesStrings.Localizable.buttonClose) {
+ dismiss()
+ }
+ .mailButtonStyle(.link)
+ .mailButtonFullWidth(true)
+ }
+ .padding(.horizontal, UIConstants.bottomSheetHorizontalPadding)
+ .padding(.top, 16)
+ .matomoView(view: ["LockedMailboxView"])
+ }
+}
+
+struct LockedMailboxView_Previews: PreviewProvider {
+ static var previews: some View {
+ Text("Preview")
+ .floatingPanel(isPresented: .constant(true)) {
+ LockedMailboxView(lockedMailbox: PreviewHelper.sampleMailbox)
+ }
+ }
+}
diff --git a/Mail/Views/Unavailable Mailbox/UnavailableMailboxesView.swift b/Mail/Views/Unavailable Mailbox/UnavailableMailboxesView.swift
new file mode 100644
index 000000000..6062d0bf9
--- /dev/null
+++ b/Mail/Views/Unavailable Mailbox/UnavailableMailboxesView.swift
@@ -0,0 +1,102 @@
+/*
+ Infomaniak Mail - iOS App
+ Copyright (C) 2022 Infomaniak Network SA
+
+ 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 .
+ */
+
+import InfomaniakCoreUI
+import InfomaniakDI
+import MailCore
+import MailResources
+import SwiftUI
+
+struct UnavailableMailboxesView: View {
+ @LazyInjectService private var matomo: MatomoUtils
+
+ @Environment(\.window) private var window
+
+ @State var isShowingNewAccountView = false
+ @State private var showAddMailbox = false
+
+ var body: some View {
+ NavigationView {
+ VStack(spacing: 16) {
+ ScrollView {
+ VStack(spacing: 16) {
+ MailResourcesAsset.logoText.swiftUIImage
+ .resizable()
+ .scaledToFit()
+ .frame(height: UIConstants.onboardingLogoHeight)
+ .padding(.top, UIConstants.onboardingLogoPaddingTop)
+
+ MailResourcesAsset.mailboxError.swiftUIImage
+ .resizable()
+ .scaledToFit()
+ .frame(height: 64)
+ Text(MailResourcesStrings.Localizable.lockedMailboxTitlePlural)
+ .textStyle(.header2)
+ .multilineTextAlignment(.center)
+ Text(MailResourcesStrings.Localizable.lockedMailboxDescriptionPlural)
+ .textStyle(.bodySecondary)
+ .multilineTextAlignment(.center)
+ .padding(.top, 24)
+
+ UnavailableMailboxListView()
+ }
+ }
+ Spacer()
+
+ NavigationLink(isActive: $showAddMailbox) {
+ AddMailboxView()
+ } label: {
+ MailButton(label: MailResourcesStrings.Localizable.buttonAddEmailAddress) {
+ matomo.track(eventWithCategory: .noValidMailboxes, name: "addMailbox")
+ showAddMailbox.toggle()
+ }
+ .mailButtonFullWidth(true)
+ .mailButtonStyle(.large)
+ }
+
+ NavigationLink {
+ AccountListView()
+ } label: {
+ Text(MailResourcesStrings.Localizable.buttonAccountSwitch)
+ .textStyle(.bodyMediumAccent)
+ }
+ .simultaneousGesture(
+ TapGesture()
+ .onEnded {
+ matomo.track(eventWithCategory: .noValidMailboxes, name: "switchAccount")
+ }
+ )
+ }
+ .padding(.horizontal, 16)
+ .frame(maxWidth: 900)
+ .matomoView(view: ["UnavailableMailboxesView"])
+ }
+ .navigationViewStyle(.stack)
+ .fullScreenCover(isPresented: $isShowingNewAccountView) {
+ AppDelegate.orientationLock = .all
+ } content: {
+ OnboardingView(page: 4, isScrollEnabled: false)
+ }
+ }
+}
+
+struct UnavailableMailboxesView_Previews: PreviewProvider {
+ static var previews: some View {
+ UnavailableMailboxesView()
+ }
+}
diff --git a/Mail/Views/Unavailable Mailbox/UpdateMailboxPasswordView.swift b/Mail/Views/Unavailable Mailbox/UpdateMailboxPasswordView.swift
new file mode 100644
index 000000000..0beded17c
--- /dev/null
+++ b/Mail/Views/Unavailable Mailbox/UpdateMailboxPasswordView.swift
@@ -0,0 +1,133 @@
+/*
+ Infomaniak Mail - iOS App
+ Copyright (C) 2022 Infomaniak Network SA
+
+ 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 .
+ */
+
+import InfomaniakCoreUI
+import InfomaniakDI
+import MailCore
+import MailResources
+import SwiftUI
+
+struct UpdateMailboxPasswordView: View {
+ @LazyInjectService private var matomo: MatomoUtils
+
+ @Environment(\.window) private var window
+
+ @State private var updatedMailboxPassword = ""
+ @State private var isShowingError = false
+ @State private var isLoading = false
+
+ private var disableButton: Bool {
+ return isLoading || showPasswordLengthWarning
+ }
+
+ private var showPasswordLengthWarning: Bool {
+ return !updatedMailboxPassword.isEmpty && (updatedMailboxPassword.count < 5 || updatedMailboxPassword.count > 80)
+ }
+
+ let mailbox: Mailbox
+ var body: some View {
+ VStack(alignment: .leading, spacing: 32) {
+ VStack(alignment: .leading, spacing: 8) {
+ Text(MailResourcesStrings.Localizable.enterPasswordDescription(mailbox.email))
+ MailButton(label: MailResourcesStrings.Localizable.buttonDetachMailbox) {
+ matomo.track(eventWithCategory: .invalidPasswordMailbox, name: "detachMailbox")
+ detachAddress()
+ }
+ .mailButtonStyle(.link)
+ .disabled(isLoading)
+ }
+
+ VStack(alignment: .leading) {
+ SecureField(MailResourcesStrings.Localizable.enterPasswordTitle, text: $updatedMailboxPassword)
+ .textContentType(.password)
+ .padding(.vertical, 12)
+ .padding(.horizontal, 16)
+ .overlay {
+ RoundedRectangle(cornerRadius: 4, style: .continuous)
+ .stroke(
+ isShowingError ? MailResourcesAsset.redColor.swiftUIColor : MailResourcesAsset.elementsColor
+ .swiftUIColor,
+ lineWidth: 1
+ )
+ }
+ .disabled(isLoading)
+
+ if isShowingError {
+ Text(MailResourcesStrings.Localizable.errorInvalidCredentials)
+ .textStyle(.labelError)
+ } else if showPasswordLengthWarning {
+ Text(MailResourcesStrings.Localizable.errorMailboxPasswordLength)
+ .textStyle(.labelSecondary)
+ }
+ }
+
+ MailButton(label: MailResourcesStrings.Localizable.buttonConfirm) {
+ matomo.track(eventWithCategory: .invalidPasswordMailbox, name: "updatePassword")
+ updateMailboxPassword()
+ }
+ .mailButtonFullWidth(true)
+ .disabled(isLoading)
+
+ MailButton(label: MailResourcesStrings.Localizable.buttonPasswordForgotten) {
+ // Empty for now, WIP
+ }
+ .mailButtonStyle(.link)
+ .mailButtonFullWidth(true)
+ .hidden()
+
+ Spacer()
+ }
+ .padding()
+ .navigationBarTitleDisplayMode(.inline)
+ .navigationTitle(MailResourcesStrings.Localizable.enterPasswordTitle)
+ .sheetViewStyle()
+ .matomoView(view: ["UpdateMailboxPasswordView"])
+ }
+
+ func updateMailboxPassword() {
+ Task {
+ isLoading = true
+ do {
+ try await AccountManager.instance.updateMailboxPassword(mailbox: mailbox, password: updatedMailboxPassword)
+ //await (window?.windowScene?.delegate as? SceneDelegate)?.showMainView()
+ } catch {
+ isShowingError = true
+ }
+ isLoading = false
+ }
+ }
+
+ func detachAddress() {
+ Task {
+ isLoading = true
+ do {
+ try await AccountManager.instance.detachMailbox(mailbox: mailbox)
+ //await (window?.windowScene?.delegate as? SceneDelegate)?.showMainView()
+ } catch {
+ isShowingError = true
+ }
+ isLoading = false
+ }
+ }
+}
+
+struct UpdateMailboxPasswordView_Previews: PreviewProvider {
+ static var previews: some View {
+ UpdateMailboxPasswordView(mailbox: PreviewHelper.sampleMailbox)
+ }
+}
diff --git a/MailCore/API/Endpoint.swift b/MailCore/API/Endpoint.swift
index 936b4882d..68d1c0eeb 100644
--- a/MailCore/API/Endpoint.swift
+++ b/MailCore/API/Endpoint.swift
@@ -83,6 +83,15 @@ public extension Endpoint {
return .base.appending(path: "/securedProxy/profile/workspace/mailbox")
}
+ static func updateMailboxPassword(mailboxId: Int) -> Endpoint {
+ return .base
+ .appending(path: "/securedProxy/cache/invalidation/profile/workspace/mailbox/\(mailboxId)/update_password")
+ }
+
+ static func detachMailbox(mailboxId: Int) -> Endpoint {
+ return addMailbox.appending(path: "/\(mailboxId)")
+ }
+
static func backups(hostingId: Int, mailboxName: String) -> Endpoint {
return .baseManager.appending(path: "/\(hostingId)/mailboxes/\(mailboxName)/backups")
}
diff --git a/MailCore/API/MailApiFetcher.swift b/MailCore/API/MailApiFetcher.swift
index 1b004afc1..6390e6ebb 100644
--- a/MailCore/API/MailApiFetcher.swift
+++ b/MailCore/API/MailApiFetcher.swift
@@ -93,6 +93,18 @@ public class MailApiFetcher: ApiFetcher {
)).data
}
+ public func updateMailboxPassword(mailbox: Mailbox, password: String) async throws -> Bool {
+ try await perform(request: authenticatedRequest(
+ .updateMailboxPassword(mailboxId: mailbox.mailboxId),
+ method: .put,
+ parameters: ["password": password]
+ )).data
+ }
+
+ public func detachMailbox(mailbox: Mailbox) async throws -> Bool {
+ try await perform(request: authenticatedRequest(.detachMailbox(mailboxId: mailbox.mailboxId), method: .delete)).data
+ }
+
func permissions(mailbox: Mailbox) async throws -> MailboxPermissions {
try await perform(request: authenticatedRequest(.permissions(mailbox: mailbox))).data
}
diff --git a/MailCore/Cache/AccountManager.swift b/MailCore/Cache/AccountManager.swift
index ca929229e..949a0fe90 100644
--- a/MailCore/Cache/AccountManager.swift
+++ b/MailCore/Cache/AccountManager.swift
@@ -107,7 +107,7 @@ public class AccountManager: RefreshTokenDelegate, ObservableObject {
public var currentMailboxManager: MailboxManager? {
if let currentMailboxManager = getMailboxManager(for: currentMailboxId, userId: currentUserId) {
return currentMailboxManager
- } else if let newCurrentMailbox = mailboxes.first {
+ } else if let newCurrentMailbox = mailboxes.first(where: { $0.isAvailable }) {
setCurrentMailboxForCurrentAccount(mailbox: newCurrentMailbox)
return getMailboxManager(for: newCurrentMailbox)
} else {
@@ -150,10 +150,7 @@ public class AccountManager: RefreshTokenDelegate, ObservableObject {
if let account = account(for: currentUserId) ?? accounts.first {
setCurrentAccount(account: account)
- if let currentMailbox = MailboxInfosManager.instance
- .getMailbox(id: currentMailboxId, userId: currentUserId) ?? mailboxes.first {
- setCurrentMailboxForCurrentAccount(mailbox: currentMailbox)
- }
+ switchToFirstValidMailboxManager()
}
}
@@ -271,7 +268,7 @@ public class AccountManager: RefreshTokenDelegate, ObservableObject {
throw MailError.noMailbox
}
- matomo.track(eventWithCategory: .userInfo, name: "nbMailboxes", value: Float(mailboxesResponse.count))
+ matomo.track(eventWithCategory: .userInfo, action: .data, name: "nbMailboxes", value: Float(mailboxesResponse.count))
let newAccount = Account(apiToken: token)
newAccount.user = user
@@ -333,6 +330,10 @@ public class AccountManager: RefreshTokenDelegate, ObservableObject {
MailboxManager.deleteUserMailbox(userId: user.id, mailboxId: mailboxRemoved.mailboxId)
}
+ if currentMailboxManager?.mailbox.isAvailable == false {
+ switchToFirstValidMailboxManager()
+ }
+
saveAccounts()
}
@@ -369,6 +370,23 @@ public class AccountManager: RefreshTokenDelegate, ObservableObject {
}
}
+ public func switchToFirstValidMailboxManager() {
+ // Current mailbox is valid
+ if let firstValidMailboxManager = currentMailboxManager,
+ !firstValidMailboxManager.mailbox.isLocked && firstValidMailboxManager.mailbox.isPasswordValid {
+ return
+ }
+
+ // At least one mailbox is valid
+ if let firstValidMailbox = mailboxes.first(where: { !$0.isLocked && $0.isPasswordValid && $0.userId == currentUserId }) {
+ switchMailbox(newMailbox: firstValidMailbox)
+ return
+ }
+
+ // No valid mailbox for current user
+ currentMailboxId = 0
+ }
+
public func switchAccount(newAccount: Account) {
setCurrentAccount(account: newAccount)
if let defaultMailbox = (mailboxes.first(where: { $0.isPrimary }) ?? mailboxes.first) {
@@ -407,6 +425,18 @@ public class AccountManager: RefreshTokenDelegate, ObservableObject {
switchMailbox(newMailbox: addedMailbox)
}
+ public func updateMailboxPassword(mailbox: Mailbox, password: String) async throws {
+ guard let apiFetcher = currentApiFetcher else { return }
+ _ = try await apiFetcher.updateMailboxPassword(mailbox: mailbox, password: password)
+ try await updateUser(for: currentAccount)
+ }
+
+ public func detachMailbox(mailbox: Mailbox) async throws {
+ guard let apiFetcher = currentApiFetcher else { return }
+ _ = try await apiFetcher.detachMailbox(mailbox: mailbox)
+ try await updateUser(for: currentAccount)
+ }
+
public func setCurrentAccount(account: Account) {
currentAccount = account
currentUserId = account.userId
diff --git a/MailCore/Cache/DraftContentManager.swift b/MailCore/Cache/DraftContentManager.swift
new file mode 100644
index 000000000..db4c8dea4
--- /dev/null
+++ b/MailCore/Cache/DraftContentManager.swift
@@ -0,0 +1,178 @@
+/*
+ Infomaniak Mail - iOS App
+ Copyright (C) 2022 Infomaniak Network SA
+
+ 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 .
+ */
+
+import Foundation
+import Sentry
+
+public class DraftContentManager: ObservableObject {
+ struct CompleteDraftResult {
+ let body: String
+ let attachments: [Attachment]
+ let shouldAddSignatureText: Bool
+ }
+
+ let messageReply: MessageReply?
+ let mailboxManager: MailboxManager
+ let incompleteDraft: Draft
+
+ public init(incompleteDraft: Draft, messageReply: MessageReply?, mailboxManager: MailboxManager) {
+ self.incompleteDraft = incompleteDraft.freezeIfNeeded()
+ self.messageReply = messageReply
+ self.mailboxManager = mailboxManager
+ }
+
+ public func prepareCompleteDraft() async throws {
+ async let draftBodyResult = try await loadCompleteDraftBody()
+ async let signature = try await loadDefaultRemoteSignature()
+
+ try await writeCompleteDraft(
+ completeBody: draftBodyResult.body,
+ signature: signature,
+ shouldAddSignatureText: draftBodyResult.shouldAddSignatureText,
+ attachments: draftBodyResult.attachments
+ )
+ }
+
+ private func loadCompleteDraftBody() async throws -> CompleteDraftResult {
+ var completeDraftBody: String
+ var attachments = [Attachment]()
+ let shouldAddSignatureText: Bool
+
+ if let messageReply {
+ // New draft created either with reply or forward
+ async let completeDraftReplyingBody = try await loadReplyingBody(
+ message: messageReply.message,
+ replyMode: messageReply.replyMode
+ )
+ async let replyingAttachments = try await loadReplyingAttachments(
+ message: messageReply.message,
+ replyMode: messageReply.replyMode
+ )
+
+ completeDraftBody = try await completeDraftReplyingBody
+ attachments = try await replyingAttachments
+ shouldAddSignatureText = true
+ } else if incompleteDraft.messageUid != nil && incompleteDraft.remoteUUID.isEmpty {
+ // Draft loaded remotely
+ completeDraftBody = try await loadCompleteDraftIfNeeded()
+ shouldAddSignatureText = false
+ } else if !incompleteDraft.remoteUUID.isEmpty {
+ // Draft loaded remotely but we have it locally
+ completeDraftBody = incompleteDraft.body
+ shouldAddSignatureText = false
+ } else {
+ // New draft
+ completeDraftBody = ""
+ shouldAddSignatureText = true
+ }
+
+ return CompleteDraftResult(
+ body: completeDraftBody,
+ attachments: attachments,
+ shouldAddSignatureText: shouldAddSignatureText
+ )
+ }
+
+ private func writeCompleteDraft(
+ completeBody: String,
+ signature: Signature,
+ shouldAddSignatureText: Bool,
+ attachments: [Attachment]
+ ) throws {
+ let realm = mailboxManager.getRealm()
+ guard let liveIncompleteDraft = realm.object(ofType: Draft.self, forPrimaryKey: incompleteDraft.localUUID) else {
+ throw MailError.unknownError
+ }
+
+ try? realm.write {
+ if liveIncompleteDraft.identityId == nil || liveIncompleteDraft.identityId?.isEmpty == true {
+ liveIncompleteDraft.identityId = "\(signature.id)"
+ if shouldAddSignatureText {
+ liveIncompleteDraft.body = signature.appendSignature(to: completeBody)
+ }
+ } else {
+ liveIncompleteDraft.body = completeBody
+ }
+
+ for attachment in attachments {
+ liveIncompleteDraft.attachments.append(attachment)
+ }
+ }
+ }
+
+ private func loadDefaultRemoteSignature() async throws -> Signature {
+ do {
+ // load all signatures every time
+ try await mailboxManager.refreshAllSignatures()
+
+ // If after a refresh we have no default signature we bail
+ guard let defaultSignature = mailboxManager.getStoredSignatures().defaultSignature else {
+ throw MailError.defaultSignatureMissing
+ }
+
+ return defaultSignature.freezeIfNeeded()
+ } 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
+ }
+ }
+
+ private func loadReplyingBody(message: Message, replyMode: ReplyMode) async throws -> String {
+ if !message.fullyDownloaded {
+ try await mailboxManager.message(message: message)
+ }
+
+ guard let freshMessage = message.thaw() else { throw MailError.unknownError }
+ freshMessage.realm?.refresh()
+ return Draft.replyingBody(message: freshMessage, replyMode: replyMode)
+ }
+
+ private func loadReplyingAttachments(message: Message, replyMode: ReplyMode) async throws -> [Attachment] {
+ guard replyMode == .forward else { return [] }
+ let attachments = try await mailboxManager.apiFetcher.attachmentsToForward(
+ mailbox: mailboxManager.mailbox,
+ message: message
+ ).attachments
+
+ return attachments
+ }
+
+ private func loadCompleteDraftIfNeeded() async throws -> String {
+ guard let associatedMessage = mailboxManager.getRealm()
+ .object(ofType: Message.self, forPrimaryKey: incompleteDraft.messageUid)?.freeze()
+ else { throw MailError.localMessageNotFound }
+
+ let remoteDraft = try await mailboxManager.apiFetcher.draft(from: associatedMessage)
+
+ remoteDraft.localUUID = incompleteDraft.localUUID
+ remoteDraft.action = .save
+ remoteDraft.delay = incompleteDraft.delay
+
+ let realm = mailboxManager.getRealm()
+ try? realm.safeWrite {
+ realm.add(remoteDraft.detached(), update: .modified)
+ }
+
+ return remoteDraft.body
+ }
+}
diff --git a/MailCore/Cache/MailboxInfosManager.swift b/MailCore/Cache/MailboxInfosManager.swift
index d86feaaf0..8c5e1f374 100644
--- a/MailCore/Cache/MailboxInfosManager.swift
+++ b/MailCore/Cache/MailboxInfosManager.swift
@@ -38,7 +38,9 @@ public class MailboxInfosManager {
public func getRealm() -> Realm {
do {
- return try Realm(configuration: realmConfiguration)
+ let realm = try Realm(configuration: realmConfiguration)
+ realm.refresh()
+ return realm
} catch {
// We can't recover from this error but at least we report it correctly on Sentry
Logging.reportRealmOpeningError(error, realmConfiguration: realmConfiguration)
diff --git a/MailCore/Cache/MailboxManager.swift b/MailCore/Cache/MailboxManager.swift
index 68a5d6b65..4949b9fe9 100644
--- a/MailCore/Cache/MailboxManager.swift
+++ b/MailCore/Cache/MailboxManager.swift
@@ -575,22 +575,16 @@ public class MailboxManager: ObservableObject {
let newThread = Thread(
uid: "offlineThread\(message.uid)",
- messagesCount: 1,
- deletedMessagesCount: 0,
messages: [newMessage],
unseenMessages: 0,
from: Array(message.from.detached()),
to: Array(message.to.detached()),
- cc: Array(message.cc.detached()),
- bcc: Array(message.bcc.detached()),
date: newMessage.date,
hasAttachments: newMessage.hasAttachments,
- hasSwissTransferAttachments: newMessage.hasAttachments,
hasDrafts: newMessage.isDraft,
flagged: newMessage.flagged,
answered: newMessage.answered,
- forwarded: newMessage.forwarded,
- size: newMessage.size
+ forwarded: newMessage.forwarded
)
newThread.fromSearch = true
newThread.subject = message.subject
@@ -680,7 +674,8 @@ public class MailboxManager: ObservableObject {
deletedUids: messageDeltaResult.deletedShortUids
.map { Constants.longUid(from: $0, folderId: folder.id) },
updated: messageDeltaResult.updated,
- cursor: messageDeltaResult.cursor
+ cursor: messageDeltaResult.cursor,
+ folderUnreadCount: messageDeltaResult.unreadCount
)
}
@@ -694,6 +689,9 @@ public class MailboxManager: ObservableObject {
if previousCursor == nil && messagesUids.addedShortUids.count < Constants.pageSize {
folder.completeHistoryInfo()
}
+ if let newUnreadCount = messagesUids.folderUnreadCount {
+ folder.remoteUnreadCount = newUnreadCount
+ }
folder.computeUnreadCount()
folder.cursor = messagesUids.cursor
folder.lastUpdate = Date()
@@ -1120,28 +1118,6 @@ public class MailboxManager: ObservableObject {
return realm.objects(Draft.self).where { $0.action != nil }
}
- public func draft(partialDraft: Draft) async throws {
- guard let associatedMessage = getRealm().object(ofType: Message.self, forPrimaryKey: partialDraft.messageUid)?.freeze()
- else { throw MailError.localMessageNotFound }
-
- // Get from API
- let draft = try await apiFetcher.draft(from: associatedMessage)
-
- await backgroundRealm.execute { realm in
- draft.localUUID = partialDraft.localUUID
- draft.action = .save
-
- // We made sure beforehand to have an up to date signature.
- // If the server does not return an identityId, we want to keep the original one
- draft.identityId = partialDraft.identityId ?? draft.identityId
- draft.delay = partialDraft.delay
-
- try? realm.safeWrite {
- realm.add(draft.detached(), update: .modified)
- }
- }
- }
-
public func draft(messageUid: String, using realm: Realm? = nil) -> Draft? {
let realm = realm ?? getRealm()
return realm.objects(Draft.self).where { $0.messageUid == messageUid }.first
diff --git a/MailCore/Models/Folder.swift b/MailCore/Models/Folder.swift
index 7c7ac7854..fa4f91c38 100644
--- a/MailCore/Models/Folder.swift
+++ b/MailCore/Models/Folder.swift
@@ -106,6 +106,7 @@ public class Folder: Object, Codable, Comparable, Identifiable {
@Persisted public var name: String
@Persisted public var role: FolderRole?
@Persisted public var unreadCount = 0
+ @Persisted public var remoteUnreadCount = 0
@Persisted public var isFavorite: Bool
@Persisted public var separator: String
@Persisted public var children: MutableSet
@@ -223,6 +224,7 @@ public class Folder: Object, Codable, Comparable, Identifiable {
case isFavorite
case separator
case children
+ case remoteUnreadCount = "unreadCount"
}
public convenience init(
diff --git a/MailCore/Models/Mailbox.swift b/MailCore/Models/Mailbox.swift
index 384972f17..e0d4120e0 100644
--- a/MailCore/Models/Mailbox.swift
+++ b/MailCore/Models/Mailbox.swift
@@ -58,6 +58,10 @@ public class Mailbox: Object, Codable, Identifiable {
return mailboxId
}
+ public var isAvailable: Bool {
+ return isPasswordValid && !isLocked
+ }
+
public var notificationTopicName: String {
return "mailbox-\(mailboxId)"
}
diff --git a/MailCore/Models/Message.swift b/MailCore/Models/Message.swift
index 442146954..a584e9835 100644
--- a/MailCore/Models/Message.swift
+++ b/MailCore/Models/Message.swift
@@ -79,12 +79,14 @@ public final class MessageDeltaResult: Decodable {
public let addedShortUids: [String]
public let updated: [MessageFlags]
public let cursor: String
+ public let unreadCount: Int
private enum CodingKeys: String, CodingKey {
case deletedShortUids = "deleted"
case addedShortUids = "added"
case updated
case cursor = "signature"
+ case unreadCount
}
// FIXME: Remove this constructor when mixed Int/String arrayis fixed by backend
@@ -103,6 +105,7 @@ public final class MessageDeltaResult: Decodable {
}
updated = try container.decode([MessageFlags].self, forKey: .updated)
cursor = try container.decode(String.self, forKey: .cursor)
+ unreadCount = try container.decode(Int.self, forKey: .unreadCount)
}
}
@@ -111,6 +114,7 @@ public struct MessagesUids {
public var deletedUids = [String]()
public var updated = [MessageFlags]()
public let cursor: String
+ public var folderUnreadCount: Int?
}
public class MessageFlags: Decodable {
@@ -428,23 +432,17 @@ public final class Message: Object, Decodable, Identifiable {
public func toThread() -> Thread {
let thread = Thread(
uid: "\(folderId)_\(uid)",
- messagesCount: 1,
- deletedMessagesCount: 1,
messages: [self],
unseenMessages: seen ? 0 : 1,
from: Array(from),
to: Array(to),
- cc: Array(cc),
- bcc: Array(bcc),
subject: subject,
date: date,
hasAttachments: !attachments.isEmpty,
- hasSwissTransferAttachments: false,
hasDrafts: !(draftResource?.isEmpty ?? true),
flagged: flagged,
answered: answered,
- forwarded: forwarded,
- size: size
+ forwarded: forwarded
)
thread.messageIds = linkedUids
return thread
diff --git a/MailCore/Models/Thread.swift b/MailCore/Models/Thread.swift
index 707627c1a..6af5cb01d 100644
--- a/MailCore/Models/Thread.swift
+++ b/MailCore/Models/Thread.swift
@@ -34,23 +34,17 @@ public struct ThreadResult: Decodable {
public class Thread: Object, Decodable, Identifiable {
@Persisted(primaryKey: true) public var uid: String
- @Persisted public var messagesCount: Int
- @Persisted public var deletedMessagesCount: Int
@Persisted public var messages: List
@Persisted public var unseenMessages: Int
@Persisted public var from: List
@Persisted public var to: List
- @Persisted public var cc: List
- @Persisted public var bcc: List
@Persisted public var subject: String?
@Persisted(indexed: true) public var date: Date
@Persisted public var hasAttachments: Bool
- @Persisted public var hasSwissTransferAttachments: Bool
@Persisted public var hasDrafts: Bool
@Persisted public var flagged: Bool
@Persisted public var answered: Bool
@Persisted public var forwarded: Bool
- @Persisted public var size: Int
@Persisted(originProperty: "threads") private var folders: LinkingObjects
@Persisted public var fromSearch = false
@@ -126,13 +120,11 @@ public class Thread: Object, Decodable, Identifiable {
messageIds = messages.flatMap { $0.linkedUids }.toRealmSet()
updateUnseenMessages()
from = messages.flatMap { $0.from.detached() }.toRealmList()
- size = messages.sum(of: \.size)
hasAttachments = messages.contains { $0.hasAttachments }
hasDrafts = messages.map { $0.isDraft }.contains(true)
updateFlagged()
answered = messages.map { $0.answered }.contains(true)
forwarded = messages.map { $0.forwarded }.contains(true)
- messagesCount = messages.count
messages = messages.sorted {
$0.date.compare($1.date) == .orderedAscending
@@ -205,65 +197,47 @@ public class Thread: Object, Decodable, Identifiable {
private enum CodingKeys: String, CodingKey {
case uid
- case messagesCount
- case deletedMessagesCount
case messages
case unseenMessages
case from
case to
- case cc
- case bcc
case subject
case date
case hasAttachments
- case hasSwissTransferAttachments = "hasStAttachments"
case hasDrafts
case flagged
case answered
case forwarded
- case size
}
public convenience init(
uid: String,
- messagesCount: Int,
- deletedMessagesCount: Int,
messages: [Message],
unseenMessages: Int,
from: [Recipient],
to: [Recipient],
- cc: [Recipient],
- bcc: [Recipient],
subject: String? = nil,
date: Date,
hasAttachments: Bool,
- hasSwissTransferAttachments: Bool,
hasDrafts: Bool,
flagged: Bool,
answered: Bool,
- forwarded: Bool,
- size: Int
+ forwarded: Bool
) {
self.init()
self.uid = uid
- self.messagesCount = messagesCount
- self.deletedMessagesCount = deletedMessagesCount
self.messages = messages.toRealmList()
self.unseenMessages = unseenMessages
self.from = from.toRealmList()
self.to = to.toRealmList()
- self.cc = cc.toRealmList()
- self.bcc = bcc.toRealmList()
self.subject = subject
self.date = date
self.hasAttachments = hasAttachments
- self.hasSwissTransferAttachments = hasSwissTransferAttachments
self.hasDrafts = hasDrafts
self.flagged = flagged
self.answered = answered
self.forwarded = forwarded
- self.size = size
}
}
diff --git a/MailCore/Utils/Constants.swift b/MailCore/Utils/Constants.swift
index 34a2e6bca..11e5ae046 100644
--- a/MailCore/Utils/Constants.swift
+++ b/MailCore/Utils/Constants.swift
@@ -158,7 +158,9 @@ public enum Constants {
let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String? ?? "Mail"
let release = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String? ?? "x.x"
let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String? ?? "x"
- return "\(appName) iOS version \(release)-beta\(build)"
+ let betaRelease = Bundle.main.isRunningInTestFlight ? "beta" : ""
+
+ return "\(appName) iOS version \(release)-\(betaRelease)\(build)"
}
public static let searchFolderId = "search_folder_id"
diff --git a/MailCore/Utils/Matomo+Extension.swift b/MailCore/Utils/Matomo+Extension.swift
index 1378d7a9c..430e93fb0 100644
--- a/MailCore/Utils/Matomo+Extension.swift
+++ b/MailCore/Utils/Matomo+Extension.swift
@@ -33,10 +33,12 @@ public extension MatomoUtils.View {
public extension MatomoUtils.EventCategory {
static let createFolder = MatomoUtils.EventCategory(displayName: "createFolder")
+ static let invalidPasswordMailbox = MatomoUtils.EventCategory(displayName: "invalidPasswordMailbox")
static let menuDrawer = MatomoUtils.EventCategory(displayName: "menuDrawer")
static let message = MatomoUtils.EventCategory(displayName: "message")
static let multiSelection = MatomoUtils.EventCategory(displayName: "multiSelection")
static let newMessage = MatomoUtils.EventCategory(displayName: "newMessage")
+ static let noValidMailboxes = MatomoUtils.EventCategory(displayName: "noValidMailboxes")
static let onboarding = MatomoUtils.EventCategory(displayName: "onboarding")
static let replyBottomSheet = MatomoUtils.EventCategory(displayName: "replyBottomSheet")
static let restoreEmailsBottomSheet = MatomoUtils.EventCategory(displayName: "restoreEmailsBottomSheet")
diff --git a/MailResources/Assets.xcassets/mailbox-error.imageset/Contents.json b/MailResources/Assets.xcassets/mailbox-error.imageset/Contents.json
new file mode 100644
index 000000000..123594b85
--- /dev/null
+++ b/MailResources/Assets.xcassets/mailbox-error.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "mailbox-error.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/MailResources/Assets.xcassets/mailbox-error.imageset/mailbox-error.svg b/MailResources/Assets.xcassets/mailbox-error.imageset/mailbox-error.svg
new file mode 100644
index 000000000..697ec4e11
--- /dev/null
+++ b/MailResources/Assets.xcassets/mailbox-error.imageset/mailbox-error.svg
@@ -0,0 +1,11 @@
+
diff --git a/MailResources/Localizable/de.lproj/Localizable.strings b/MailResources/Localizable/de.lproj/Localizable.strings
index 9937cb631..7f4cda23c 100644
Binary files a/MailResources/Localizable/de.lproj/Localizable.strings and b/MailResources/Localizable/de.lproj/Localizable.strings differ
diff --git a/MailResources/Localizable/en.lproj/Localizable.strings b/MailResources/Localizable/en.lproj/Localizable.strings
index 034ffeb6f..25c01f046 100644
Binary files a/MailResources/Localizable/en.lproj/Localizable.strings and b/MailResources/Localizable/en.lproj/Localizable.strings differ
diff --git a/MailResources/Localizable/es.lproj/Localizable.strings b/MailResources/Localizable/es.lproj/Localizable.strings
index 0d332b46a..c4cc91886 100644
Binary files a/MailResources/Localizable/es.lproj/Localizable.strings and b/MailResources/Localizable/es.lproj/Localizable.strings differ
diff --git a/MailResources/Localizable/fr.lproj/Localizable.strings b/MailResources/Localizable/fr.lproj/Localizable.strings
index 2707ed1a0..28583e497 100644
Binary files a/MailResources/Localizable/fr.lproj/Localizable.strings and b/MailResources/Localizable/fr.lproj/Localizable.strings differ
diff --git a/MailResources/Localizable/it.lproj/Localizable.strings b/MailResources/Localizable/it.lproj/Localizable.strings
index e4290bbdd..2e7724b88 100644
Binary files a/MailResources/Localizable/it.lproj/Localizable.strings and b/MailResources/Localizable/it.lproj/Localizable.strings differ
diff --git a/Project.swift b/Project.swift
index 5e8cf2183..cdf1d1406 100644
--- a/Project.swift
+++ b/Project.swift
@@ -22,14 +22,14 @@ import ProjectDescription
let deploymentTarget = DeploymentTarget.iOS(targetVersion: "15.0", devices: [.iphone, .ipad])
let baseSettings = SettingsDictionary()
.currentProjectVersion("1")
- .marketingVersion("1.0.0")
+ .marketingVersion("1.0.1")
.automaticCodeSigning(devTeam: "864VDCS2QY")
let project = Project(name: "Mail",
packages: [
.package(url: "https://github.com/Infomaniak/ios-login", .upToNextMajor(from: "4.0.0")),
.package(url: "https://github.com/Infomaniak/ios-dependency-injection", .upToNextMajor(from: "1.1.6")),
- .package(url: "https://github.com/Infomaniak/ios-core", .revision("e5fe8e03aa06375f20c8e40f4fae3a6af6e4d75e")),
+ .package(url: "https://github.com/Infomaniak/ios-core", .revision("ef2811a288a3a4b94dd5d04c5f47ddd771e4176c")),
.package(url: "https://github.com/Infomaniak/ios-core-ui", .upToNextMajor(from: "2.3.0")),
.package(url: "https://github.com/Infomaniak/ios-notifications", .upToNextMajor(from: "2.1.0")),
.package(url: "https://github.com/Infomaniak/ios-create-account", .upToNextMajor(from: "1.1.0")),