Skip to content

Commit

Permalink
feat: NotificationContentExtension (#1432)
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinperignon authored May 24, 2024
2 parents a1f951d + 39dc98f commit 3e80c30
Show file tree
Hide file tree
Showing 16 changed files with 429 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import SwiftUI
struct MessageHeaderSummaryView: View {
@LazyInjectService private var matomo: MatomoUtils

@Environment(\.isMessageInteractive) private var isMessageInteractive

@EnvironmentObject private var mailboxManager: MailboxManager
@EnvironmentObject private var mainViewState: MainViewState

Expand Down Expand Up @@ -59,6 +61,7 @@ struct MessageHeaderSummaryView: View {
.adaptivePanel(item: $contactViewRecipient) { recipient in
ContactActionsView(recipient: recipient)
}
.disabled(!isMessageInteractive)
}

VStack(alignment: .leading, spacing: UIPadding.verySmall) {
Expand Down Expand Up @@ -108,7 +111,7 @@ struct MessageHeaderSummaryView: View {
.foregroundStyle(MailResourcesAsset.redColor)
}

if isMessageExpanded {
if isMessageExpanded && isMessageInteractive {
HStack(spacing: 20) {
Button {
matomo.track(eventWithCategory: .messageActions, name: "reply")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import SwiftUI
struct MessageHeaderView: View {
@LazyInjectService private var matomo: MatomoUtils

@Environment(\.isMessageInteractive) private var isMessageInteractive

@EnvironmentObject private var mainViewState: MainViewState
@EnvironmentObject private var mailboxManager: MailboxManager

Expand All @@ -44,11 +46,14 @@ struct MessageHeaderView: View {

if isHeaderExpanded {
MessageHeaderDetailView(message: message)
.disabled(!isMessageInteractive)
}
}
.contentShape(Rectangle())
.padding(value: .regular)
.onTapGesture {
guard isMessageInteractive else { return }

if message.isDraft {
DraftUtils.editDraft(
from: message,
Expand Down
6 changes: 6 additions & 0 deletions Mail/Views/Thread/Message/MessageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ import MailCoreUI
import MailResources
import RealmSwift
import SwiftUI
import SwiftUIMacros

extension EnvironmentValues {
@EnvironmentKey
var isMessageInteractive = true
}

/// Something that can display an email
struct MessageView: View {
Expand Down
19 changes: 19 additions & 0 deletions MailCore/Utils/NotificationsHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,25 @@ public enum NotificationsHelper {
return totalUnreadCount
}

public static func fetchMessage(uid: String, in mailboxManager: MailboxManager) async throws -> Message? {
guard let inboxFolder = mailboxManager.getFolder(with: .inbox),
inboxFolder.cursor != nil else {
// We do nothing if we don't have an initial cursor
return nil
}
await mailboxManager.refreshFolderContent(inboxFolder.freezeIfNeeded())

@ThreadSafe var message = mailboxManager.fetchObject(ofType: Message.self, forPrimaryKey: uid)

if let message,
!message.fullyDownloaded {
try await mailboxManager.message(message: message)
}

message?.realm?.refresh()
return message?.freezeIfNeeded()
}

public static func clearAlreadyReadNotifications(shouldWait: Bool = false) async {
let notificationCenter = UNUserNotificationCenter.current()
let deliveredNotifications = await notificationCenter.deliveredNotifications()
Expand Down
44 changes: 44 additions & 0 deletions MailNotificationContentExtension/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleDisplayName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>AppIdentifierPrefix</key>
<string>$(AppIdentifierPrefix)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>UNNotificationExtensionCategory</key>
<string>com.infomaniak.mail.incoming</string>
<key>UNNotificationExtensionUserInteractionEnabled</key>
<true/>
<key>UNNotificationExtensionDefaultContentHidden</key>
<true/>
<key>UNNotificationExtensionInitialContentSizeRatio</key>
<real>1.5</real>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.content-extension</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).NotificationViewController</string>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.infomaniak.mail</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.infomaniak.mail</string>
</array>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
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 <http://www.gnu.org/licenses/>.
*/

import Foundation
import InfomaniakDI

class NotificationContentExtensionTargetAssembly: CommonAppAndShareTargetAssembly {}
104 changes: 104 additions & 0 deletions MailNotificationContentExtension/NotificationViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
Infomaniak Mail - iOS App
Copyright (C) 2024 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 <http://www.gnu.org/licenses/>.
*/

import InfomaniakDI
import MailCore
import MailResources
import RealmSwift
import SwiftUI
import UIKit
import UserNotifications
import UserNotificationsUI

class NotificationViewController: UIViewController, UNNotificationContentExtension {
private let dependencyInjectionHook = NotificationContentExtensionTargetAssembly()

@LazyInjectService private var accountManager: AccountManager
@LazyInjectService private var mailboxInfosManager: MailboxInfosManager

let activityIndicator = UIActivityIndicatorView(style: .medium)
let errorLabel = UILabel()

override func viewDidLoad() {
super.viewDidLoad()

ModelMigrator().migrateRealmIfNeeded()
SentryDebug.setUserId(accountManager.currentUserId)

activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.hidesWhenStopped = true
activityIndicator.startAnimating()
view.addSubview(activityIndicator)

errorLabel.isHidden = true
errorLabel.font = UIFont.preferredFont(forTextStyle: .body)
errorLabel.textAlignment = .center
errorLabel.textColor = .secondaryLabel
errorLabel.text = MailResourcesStrings.Localizable.errorMessageNotFound
errorLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(errorLabel)

NSLayoutConstraint.activate([
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor),
errorLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
errorLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}

func stopAnimating(displayError: Bool) {
activityIndicator.stopAnimating()
errorLabel.isHidden = displayError
}

func didReceive(_ notification: UNNotification) {
Task {
let userInfo = notification.request.content.userInfo
guard let mailboxId = userInfo[NotificationsHelper.UserInfoKeys.mailboxId] as? Int,
let userId = userInfo[NotificationsHelper.UserInfoKeys.userId] as? Int,
let messageUid = userInfo[NotificationsHelper.UserInfoKeys.messageUid] as? String,
let mailbox = mailboxInfosManager.getMailbox(id: mailboxId, userId: userId),
let mailboxManager = accountManager.getMailboxManager(for: mailbox) else {
stopAnimating(displayError: true)
return
}

guard let message = try? await NotificationsHelper.fetchMessage(uid: messageUid, in: mailboxManager) else {
stopAnimating(displayError: true)
return
}

let messageView = ScrollView {
MessageView(
message: message,
isMessageExpanded: true,
threadForcedExpansion: .constant([:])
)
.environment(\.isMessageInteractive, false)
.environmentObject(mailboxManager)
}

let hostingViewController = UIHostingController(rootView: messageView)

addChild(hostingViewController)
view.addSubview(hostingViewController.view)
hostingViewController.view.frame = view.bounds
stopAnimating(displayError: false)
}
}
}
26 changes: 26 additions & 0 deletions MailNotificationContentExtension/Proxy/ApplicationState.swift
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

import MailCore
import UIKit

public struct ApplicationState: ApplicationStatable {
public var applicationState: UIApplication.State? {
nil
}
}
27 changes: 27 additions & 0 deletions MailNotificationContentExtension/Proxy/CacheManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
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 <http://www.gnu.org/licenses/>.
*/

import Foundation
import InfomaniakCore

/// A cache manager that works in Extension mode
public final class CacheManager: CacheManageable {
public func refreshCacheData(account: Account?) {
// NOOP in shareExtension
}
}
31 changes: 31 additions & 0 deletions MailNotificationContentExtension/Proxy/MessageActionHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Infomaniak Mail - iOS App
Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.
*/

import Foundation
import MailCore

// Stub for extension mode
public struct MessageActionHandler: MessageActionHandlable {
func handleTapOnNotification(messageUid: String, mailbox: Mailbox, mailboxManager: MailboxManager) async throws {}

func handleArchiveOnNotification(messageUid: String, mailboxManager: MailboxManager) async throws {}

func handleDeleteOnNotification(messageUid: String, mailboxManager: MailboxManager) async throws {}

func handleReplyOnNotification(messageUid: String, mailbox: Mailbox, mailboxManager: MailboxManager) {}
}
Loading

0 comments on commit 3e80c30

Please sign in to comment.