diff --git a/Mail/Helpers/AppAssembly.swift b/Mail/Helpers/AppAssembly.swift
index ae924be87..48970e6f4 100644
--- a/Mail/Helpers/AppAssembly.swift
+++ b/Mail/Helpers/AppAssembly.swift
@@ -76,6 +76,12 @@ enum ApplicationAssembly {
},
Factory(type: SnackBarPresentable.self) { _, _ in
SnackBarPresenter()
+ },
+ Factory(type: MessagePresentable.self) { _, _ in
+ MessagePresenter()
+ },
+ Factory(type: ApplicationStatable.self) { _, _ in
+ ApplicationState()
}
]
diff --git a/Mail/Proxy/Implementation/ApplicationState.swift b/Mail/Proxy/Implementation/ApplicationState.swift
new file mode 100644
index 000000000..093238df3
--- /dev/null
+++ b/Mail/Proxy/Implementation/ApplicationState.swift
@@ -0,0 +1,26 @@
+/*
+ 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 UIKit
+import MailCore
+
+public struct ApplicationState: ApplicationStatable {
+ public var applicationState: UIApplication.State? {
+ UIApplication.shared.applicationState
+ }
+}
diff --git a/Mail/Views/New Message/ComposeMessageBodyView.swift b/Mail/Views/New Message/ComposeMessageBodyView.swift
index bdafe6273..b1de1aa74 100644
--- a/Mail/Views/New Message/ComposeMessageBodyView.swift
+++ b/Mail/Views/New Message/ComposeMessageBodyView.swift
@@ -23,7 +23,7 @@ import RealmSwift
import SwiftUI
struct ComposeMessageBodyView: View {
- @LazyInjectService private var snackbarPresenter: SnackBarPresentable
+ @LazyInjectService private var messagePresentable: MessagePresentable
@Environment(\.dismissModal) var dismissModal
@@ -83,7 +83,7 @@ struct ComposeMessageBodyView: View {
setSignature()
case .error:
// Unable to get signatures, "An error occurred" and close modal.
- snackbarPresenter.show(message: MailError.unknownError.localizedDescription)
+ messagePresentable.show(message: MailError.unknownError.localizedDescription)
dismissMessageView()
case .progress:
break
@@ -120,7 +120,7 @@ struct ComposeMessageBodyView: View {
isLoadingContent = false
} catch {
dismissMessageView()
- snackbarPresenter.show(message: MailError.unknownError.localizedDescription)
+ messagePresentable.show(message: MailError.unknownError.localizedDescription)
}
}
@@ -138,7 +138,7 @@ struct ComposeMessageBodyView: View {
isLoadingContent = false
} catch {
dismissMessageView()
- snackbarPresenter.show(message: MailError.unknownError.localizedDescription)
+ messagePresentable.show(message: MailError.unknownError.localizedDescription)
}
}
diff --git a/MailCore/Cache/DraftManager.swift b/MailCore/Cache/DraftManager.swift
index f0c78e2ef..7e6c876e4 100644
--- a/MailCore/Cache/DraftManager.swift
+++ b/MailCore/Cache/DraftManager.swift
@@ -73,7 +73,7 @@ public final class DraftManager {
private static let saveExpirationSec = 3
@LazyInjectService private var matomo: MatomoUtils
- @LazyInjectService private var snackbarPresenter: SnackBarPresentable
+ @LazyInjectService private var messagePresentable: MessagePresentable
/// Used by DI only
public init() {
@@ -96,13 +96,13 @@ public final class DraftManager {
try await mailboxManager.save(draft: draft)
} catch {
guard error.shouldDisplay else { return }
- snackbarPresenter.show(message: error.localizedDescription)
+ messagePresentable.show(message: error.localizedDescription)
}
await draftQueue.endBackgroundTask(uuid: draft.localUUID)
}
public func send(draft: Draft, mailboxManager: MailboxManager) async -> Date? {
- snackbarPresenter.show(message: MailResourcesStrings.Localizable.snackbarEmailSending)
+ messagePresentable.show(message: MailResourcesStrings.Localizable.snackbarEmailSending)
var sendDate: Date?
await draftQueue.cleanQueueElement(uuid: draft.localUUID)
@@ -110,10 +110,10 @@ public final class DraftManager {
do {
let cancelableResponse = try await mailboxManager.send(draft: draft)
- snackbarPresenter.show(message: MailResourcesStrings.Localizable.snackbarEmailSent)
+ messagePresentable.show(message: MailResourcesStrings.Localizable.snackbarEmailSent)
sendDate = cancelableResponse.scheduledDate
} catch {
- snackbarPresenter.show(message: error.localizedDescription)
+ messagePresentable.show(message: error.localizedDescription)
}
await draftQueue.endBackgroundTask(uuid: draft.localUUID)
return sendDate
@@ -179,11 +179,11 @@ public final class DraftManager {
}
await saveDraftRemotely(draft: draft, mailboxManager: mailboxManager)
- snackbarPresenter.show(message: MailResourcesStrings.Localizable.snackbarDraftSaved,
- action: .init(title: MailResourcesStrings.Localizable.actionDelete) { [weak self] in
- self?.matomo.track(eventWithCategory: .snackbar, name: "deleteDraft")
- self?.deleteDraftSnackBarAction(draft: draft, mailboxManager: mailboxManager)
- })
+ let messageAction: MessageAction = (MailResourcesStrings.Localizable.actionDelete, { [weak self] in
+ self?.matomo.track(eventWithCategory: .snackbar, name: "deleteDraft")
+ self?.deleteDraftSnackBarAction(draft: draft, mailboxManager: mailboxManager)
+ })
+ messagePresentable.show(message: MailResourcesStrings.Localizable.snackbarDraftSaved, action: messageAction)
}
private func refreshDraftFolder(latestSendDate: Date?, mailboxManager: MailboxManager) async throws {
@@ -209,7 +209,7 @@ public final class DraftManager {
await tryOrDisplayError {
if let liveDraft = draft.thaw() {
try await mailboxManager.delete(draft: liveDraft.freeze())
- snackbarPresenter.show(message: MailResourcesStrings.Localizable.snackbarDraftDeleted)
+ messagePresentable.show(message: MailResourcesStrings.Localizable.snackbarDraftDeleted)
if let draftFolder = mailboxManager.getFolder(with: .draft)?.freeze() {
await mailboxManager.refresh(folder: draftFolder)
}
diff --git a/MailCore/Utils/ApplicationStatable.swift b/MailCore/Utils/ApplicationStatable.swift
new file mode 100644
index 000000000..52651173b
--- /dev/null
+++ b/MailCore/Utils/ApplicationStatable.swift
@@ -0,0 +1,26 @@
+/*
+ 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 UIKit
+
+// TODO: Move to CoreUI
+
+/// Something that reads the application state if available
+public protocol ApplicationStatable {
+ var applicationState: UIApplication.State? { get }
+}
diff --git a/MailCore/Utils/MessagePresentable.swift b/MailCore/Utils/MessagePresentable.swift
new file mode 100644
index 000000000..83e5d4342
--- /dev/null
+++ b/MailCore/Utils/MessagePresentable.swift
@@ -0,0 +1,116 @@
+/*
+ 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 CocoaLumberjackSwift
+import Foundation
+import InfomaniakCore
+import InfomaniakCoreUI
+import InfomaniakDI
+import UserNotifications
+
+// TODO: Move to CoreUI / use with kDrive
+
+/// Something that can present a message to the user, abstracted of execution context (App / NSExtension)
+///
+/// Will present a snackbar while main app is opened, a local notification in Extension or Background.
+public protocol MessagePresentable {
+ /// Will present a snackbar while main app is opened, a local notification in Extension or Background.
+ /// - Parameter message: The message to display
+ func show(message: String)
+
+ /// Will present a snackbar while main app is opened, a local notification in Extension or Background.
+ /// - Parameters:
+ /// - message: The message to display
+ /// - action: Title and closure associated with the action
+ func show(message: String, action: MessageAction)
+}
+
+public typealias MessageAction = (name: String, closure: () -> Void)
+
+public final class MessagePresenter: MessagePresentable {
+ @LazyInjectService private var snackbarPresenter: SnackBarPresentable
+ @LazyInjectService private var applicationState: ApplicationStatable
+
+ /// Used by DI
+ public init() {
+ // META: keep sonarcloud happy
+ }
+
+ // MARK: - MessagePresentable
+
+ public func show(message: String) {
+ showInContext(message: message, action: nil)
+ }
+
+ public func show(message: String, action: MessageAction) {
+ showInContext(message: message, action: action)
+ }
+
+ // MARK: - private
+
+ private func showInContext(message: String, action: MessageAction?) {
+ // check not in extension mode
+ guard !Bundle.main.isExtension else {
+ presentInLocalNotification(message: message, action: action)
+ return
+ }
+
+ // if app not in foreground, we use the local notifications
+ guard applicationState.applicationState == .active else {
+ presentInLocalNotification(message: message, action: action)
+ return
+ }
+
+ // Present the message as we are in foreground app context
+ presentInSnackbar(message: message, action: action)
+ }
+
+ // MARK: Private
+
+ private func presentInSnackbar(message: String, action: MessageAction?) {
+ guard let action = action else {
+ snackbarPresenter.show(message: message)
+ return
+ }
+
+ let snackBarAction = IKSnackBar.Action(title: action.name, action: action.closure)
+ snackbarPresenter.show(message: message, action: snackBarAction)
+ }
+
+ private func presentInLocalNotification(message: String, action: MessageAction?) {
+ if action != nil {
+ DDLogError("Action not implemented in notifications for now")
+ }
+
+ let content = UNMutableNotificationContent()
+ content.body = message
+
+ let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.3, repeats: false)
+ let uuidString = UUID().uuidString
+ let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
+ let notificationCenter = UNUserNotificationCenter.current()
+ notificationCenter.add(request) { error in
+ DDLogError("MessagePresenter local notification error:\(String(describing: error)) ")
+ }
+
+ // Self destruct this notification, as used only for user feedback
+ DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) {
+ notificationCenter.removeDeliveredNotifications(withIdentifiers: [uuidString])
+ }
+ }
+}
diff --git a/MailShareExtension/Proxy/ApplicationState.swift b/MailShareExtension/Proxy/ApplicationState.swift
new file mode 100644
index 000000000..43c701d41
--- /dev/null
+++ b/MailShareExtension/Proxy/ApplicationState.swift
@@ -0,0 +1,26 @@
+/*
+ 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 UIKit
+import MailCore
+
+public struct ApplicationState: ApplicationStatable {
+ public var applicationState: UIApplication.State? {
+ nil
+ }
+}