Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mail share extension #830

Merged
merged 70 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
b94730b
feat: initial project setup share extension
adrien-coye Jun 22, 2023
37c4899
Merge branch 'master' into shareExtension
adrien-coye Jun 27, 2023
61b028d
feat: wrap SwUI view on share ext open
adrien-coye Jun 27, 2023
15e3769
feat(WIP): split code so views can be embedded in share ext
adrien-coye Jun 28, 2023
3b9571e
feat: kMail share extension builds with all classes of the main proje…
adrien-coye Jun 29, 2023
695e842
feat: extract and factorise DI loading code
adrien-coye Jun 29, 2023
76805e5
feat: share extension dismiss() working with a NSNotification
adrien-coye Jun 30, 2023
2824782
feat: reworked dismiss to work with environment
adrien-coye Jun 30, 2023
1cb7146
feat: open ikMail Scheme
adrien-coye Jul 3, 2023
2a69a5e
Merge branch 'master' into shareExtension
adrien-coye Jul 3, 2023
aeeb83f
feat: dismiss share ext if loged out on tap, as open deeplink does no…
adrien-coye Jul 3, 2023
b394684
feat: fix empty contact list in share extension
adrien-coye Jul 3, 2023
94235a7
feat: SnackBar works in Extension with DI
adrien-coye Jul 3, 2023
14b2112
fix(draft): dismiss of draft editor works in app context
adrien-coye Jul 4, 2023
417b0f3
feat: use app wise @LazyInjectService private var snackbarPresenter
adrien-coye Jul 4, 2023
06cf460
feat(shareExtension): what is displayed with a snackbar in the app is…
adrien-coye Jul 4, 2023
f337a69
feat: make sure we call applicationState from the main thread
adrien-coye Jul 4, 2023
49e35cd
feat: save current draft only on share ext close
adrien-coye Jul 5, 2023
de91316
feat: removed ikmail scheme
adrien-coye Jul 5, 2023
bd4fced
fix(shareExt): Realm issue
adrien-coye Jul 5, 2023
a8df84e
fix(draft): draft is not empty if has attachments
adrien-coye Jul 5, 2023
c896c0e
Merge branch 'master' into shareExtension
adrien-coye Jul 6, 2023
d995a4f
chore: SonarCloud feedback
adrien-coye Jul 6, 2023
2f67324
fix(attachments): performance and scallability issues
adrien-coye Jul 7, 2023
920ce9d
Merge branch 'master' into shareExtension
adrien-coye Jul 7, 2023
1f168a5
Merge branch 'master' into shareExtension
adrien-coye Jul 12, 2023
bff2a6b
chore: bump Core lib
adrien-coye Jul 12, 2023
bf0d4df
feat(share_ext): hooking into Core lib components
adrien-coye Jul 13, 2023
a7aa19d
chore: Post merge cleanup
adrien-coye Jul 13, 2023
da6778a
feat(catalyst): quickwin use iPadLandscape for mac catalyst
adrien-coye Jul 13, 2023
9feba22
fix(NotificationExtension): Fix missing DI in notification extension
adrien-coye Jul 14, 2023
a0fdce8
Merge branch 'master' into shareExtension
adrien-coye Jul 19, 2023
4ccbfe6
chore: use code in Core and CoreUI, remove local copy
adrien-coye Jul 19, 2023
65caad4
chore: Bump Core / Core UI
adrien-coye Jul 19, 2023
54fdd9d
chore: bump Core
adrien-coye Jul 26, 2023
7a87d44
Merge branch 'master' into shareExtension
adrien-coye Jul 26, 2023
5e7adcf
chore: PR Feedback
adrien-coye Jul 26, 2023
efef3f8
chore: PR Feedback
adrien-coye Jul 26, 2023
3861d6f
chore: bump core
adrien-coye Jul 28, 2023
8b20ab8
Merge branch 'master' into shareExtension
adrien-coye Jul 28, 2023
3028b87
chore: fix merge
adrien-coye Jul 28, 2023
73e9007
Merge branch 'master' into shareExtension
adrien-coye Jul 28, 2023
4216019
chore: PR Feedback
adrien-coye Jul 28, 2023
1da29b4
fix(save draft): now works in share ext, was broken by a refactor on …
adrien-coye Jul 28, 2023
ff57e03
chore: PR Feedback
adrien-coye Aug 2, 2023
e5f1496
chore: PR Feedback
adrien-coye Aug 2, 2023
ab41ae9
chore: i18n bump
adrien-coye Aug 2, 2023
90b104e
chore: PR Feedback
adrien-coye Aug 2, 2023
5671159
chore: PR Feedback
adrien-coye Aug 2, 2023
95747ae
chore(URLNavigable): PR Feedback
adrien-coye Aug 2, 2023
d759108
chore: PR Feedback
adrien-coye Aug 2, 2023
c4786a3
chore: PR Feedback. (IKSnackBarAvoider/ShareViewController)
adrien-coye Aug 2, 2023
f0883f3
fix: crash in NotificationServiceAssembly
adrien-coye Aug 2, 2023
986854d
fix(sending snackbar): Now when sending / saving / discarding a draft…
adrien-coye Aug 2, 2023
02a8748
chore: swiftformat .
adrien-coye Aug 3, 2023
8716477
fix(mac): SplitViewController configuration works
adrien-coye Aug 3, 2023
de28d6a
fix(theme): Share extension conform to theme
adrien-coye Aug 3, 2023
8968983
chore: PR Feedback
adrien-coye Aug 3, 2023
93ba208
fix: Share ext entitlement
adrien-coye Aug 3, 2023
0c1afa8
fix: Logger requires DI to be set up
adrien-coye Aug 3, 2023
08d916e
refactor: renamed MessagePresentable to UserAlertDisplayable
adrien-coye Aug 3, 2023
7c28709
chore: bump core
adrien-coye Aug 3, 2023
a09ccc1
fix(ComposeMessageView): navigationViewStyle .stack for iPad display
adrien-coye Aug 3, 2023
435fe13
refactor: directly use openURL()
adrien-coye Aug 3, 2023
067450c
chore: bump core
adrien-coye Aug 3, 2023
fb53d61
fix(ShareViewController.swift): use tint color primary
adrien-coye Aug 3, 2023
1b1c81d
chore: PR Feedback
adrien-coye Aug 3, 2023
42712e4
chore: remove extraneous copy statement in project configuration
adrien-coye Aug 3, 2023
08bedb1
fix(parsing): making sure we decode dates as iso8601 in app _and_ ext…
adrien-coye Aug 3, 2023
2e3aeeb
refactor: use common way to send a mail
adrien-coye Aug 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions Mail/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,27 @@
*/

import CocoaLumberjackSwift
import InfomaniakCore
import InfomaniakDI
import InfomaniakNotifications
import MailCore
import UIKit

class AppDelegate: UIResponder, UIApplicationDelegate {
@available(iOSApplicationExtension, unavailable)
final class AppDelegate: UIResponder, UIApplicationDelegate {
private let notificationCenterDelegate = NotificationCenterDelegate()
static var orientationLock = UIInterfaceOrientationMask.all

func application(
_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
@LazyInjectService private var orientationManager: OrientationManageable
@LazyInjectService private var accountManager: AccountManager
@LazyInjectService private var applicationState: ApplicationStatable

/// Making sure the DI is registered at a very early stage of the app launch.
private let dependencyInjectionHook = EarlyDIHook()

func application(_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
DDLogInfo("Application starting in foreground ? \(applicationState.applicationState != .background)")

UNUserNotificationCenter.current().delegate = notificationCenterDelegate
Task {
// Ask permission app launch
Expand All @@ -44,14 +52,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
@InjectService var notificationService: InfomaniakNotifications
@InjectService var tokenStore: TokenStore

for account in AccountManager.instance.accounts {
for account in accountManager.accounts {
Task {
/* Because of a backend issue we can't register the notification token directly after the creation or refresh of
an API token. We wait at least 15 seconds before trying to register. */
try? await Task.sleep(nanoseconds: 15_000_000_000)

guard let token = tokenStore.tokenFor(userId: account.userId) else { return }
let userApiFetcher = AccountManager.instance.getApiFetcher(for: token.userId, token: token)
let userApiFetcher = accountManager.getApiFetcher(for: token.userId, token: token)
await notificationService.updateRemoteNotificationsToken(tokenData: deviceToken,
userApiFetcher: userApiFetcher,
updatePolicy: .always)
Expand All @@ -65,6 +73,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication,
supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return AppDelegate.orientationLock
return orientationManager.orientationLock
}
}
11 changes: 9 additions & 2 deletions Mail/Components/UnavailableMailboxListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import InfomaniakDI
import MailCore
import MailResources
import RealmSwift
Expand All @@ -27,14 +28,20 @@ struct UnavailableMailboxListView: View {
@ObservedResults(
Mailbox.self,
configuration: MailboxInfosManager.instance.realmConfiguration,
where: { $0.userId == AccountManager.instance.currentUserId && $0.isPasswordValid == false },
where: { mailbox in
@InjectService var accountManager: AccountManager
return mailbox.userId == accountManager.currentUserId && mailbox.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 },
where: { mailbox in
@InjectService var accountManager: AccountManager
return mailbox.userId == accountManager.currentUserId && mailbox.isLocked == true
},
sortDescriptor: SortDescriptor(keyPath: \Mailbox.mailboxId)
) private var lockedMailboxes

Expand Down
138 changes: 138 additions & 0 deletions Mail/Helpers/AppAssembly.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
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 InfomaniakBugTracker
import InfomaniakCore
import InfomaniakCoreUI
import InfomaniakDI
import InfomaniakLogin
import InfomaniakNotifications
import MailCore

private let realmRootPath = "mailboxes"
private let appGroupIdentifier = "group.com.infomaniak.mail"

extension Array where Element == Factory {
func registerFactoriesInDI() {
forEach { SimpleResolver.sharedResolver.store(factory: $0) }
}
}

/// Something that prepares the application Dependency Injection
enum ApplicationAssembly {
static func setupDI() {
// Setup main servicies
setupMainServices()

// Setup proxy types necessary for the App code to work in Extension mode
setupProxyTypes()
}

private static func setupMainServices() {
let factories = [
Factory(type: InfomaniakNetworkLoginable.self) { _, _ in
InfomaniakNetworkLogin(clientId: MailApiFetcher.clientId)
},
Factory(type: InfomaniakLoginable.self) { _, _ in
InfomaniakLogin(clientId: MailApiFetcher.clientId)
},
Factory(type: KeychainHelper.self) { _, _ in
KeychainHelper(accessGroup: AccountManager.accessGroup)
},
Factory(type: InfomaniakNotifications.self) { _, _ in
InfomaniakNotifications(appGroup: AccountManager.appGroup)
},
Factory(type: AppLockHelper.self) { _, _ in
AppLockHelper()
},
Factory(type: BugTracker.self) { _, _ in
BugTracker(info: BugTrackerInfo(project: "app-mobile-mail", gitHubRepoName: "ios-mail", appReleaseType: .beta))
},
Factory(type: MatomoUtils.self) { _, _ in
MatomoUtils(siteId: Constants.matomoId, baseURL: URLConstants.matomo.url)
},
Factory(type: IKSnackBarAvoider.self) { _, _ in
IKSnackBarAvoider()
},
Factory(type: DraftManager.self) { _, _ in
DraftManager()
},
Factory(type: AccountManager.self) { _, _ in
AccountManager()
},
Factory(type: SnackBarPresentable.self) { _, _ in
SnackBarPresenter()
},
Factory(type: UserAlertDisplayable.self) { _, _ in
UserAlertDisplayer()
},
Factory(type: ApplicationStatable.self) { _, _ in
ApplicationState()
},
Factory(type: UserActivityController.self) { _, _ in
UserActivityController()
},
Factory(type: PlatformDetectable.self) { _, _ in
PlatformDetector()
},
Factory(type: AppGroupPathProvidable.self) { _, _ in
guard let provider = AppGroupPathProvider(
realmRootPath: realmRootPath,
appGroupIdentifier: appGroupIdentifier
) else {
fatalError("could not safely init AppGroupPathProvider")
}

return provider
},
Factory(type: TokenStore.self) { _, _ in
TokenStore()
}
]

factories.registerFactoriesInDI()
}

private static func setupProxyTypes() {
let factories = [
Factory(type: CacheManageable.self) { _, _ in
CacheManager()
},
Factory(type: OrientationManageable.self) { _, _ in
OrientationManager()
},
Factory(type: RemoteNotificationRegistrable.self) { _, _ in
RemoteNotificationRegistrer()
}
]

factories.registerFactoriesInDI()
}
}

/// Something that loads the DI on init
public struct EarlyDIHook {
public init() {
// setup DI ASAP
ApplicationAssembly.setupDI()

// Setup debug stack early, requires DI to be setup to work
Logging.initLogging()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

import Foundation
import InfomaniakDI
import MailCore
import UIKit
import UserNotifications
Expand All @@ -26,24 +27,26 @@ public struct NotificationTappedPayload {
}

@MainActor
class NotificationCenterDelegate: NSObject, UNUserNotificationCenterDelegate {
final class NotificationCenterDelegate: NSObject, UNUserNotificationCenterDelegate {
@LazyInjectService private var accountManager: AccountManager

private func handleClickOnNotification(scene: UIScene?, content: UNNotificationContent) {
guard let mailboxId = content.userInfo[NotificationsHelper.UserInfoKeys.mailboxId] as? Int,
let userId = content.userInfo[NotificationsHelper.UserInfoKeys.userId] as? Int,
let mailbox = MailboxInfosManager.instance.getMailbox(id: mailboxId, userId: userId),
let mailboxManager = AccountManager.instance.getMailboxManager(for: mailbox) else {
let mailboxManager = accountManager.getMailboxManager(for: mailbox) else {
return
}

if AccountManager.instance.currentMailboxManager?.mailbox != mailboxManager.mailbox {
if AccountManager.instance.getCurrentAccount()?.userId != mailboxManager.mailbox.userId {
if let switchedAccount = AccountManager.instance.accounts.values
if accountManager.currentMailboxManager?.mailbox != mailboxManager.mailbox {
if accountManager.getCurrentAccount()?.userId != mailboxManager.mailbox.userId {
if let switchedAccount = accountManager.accounts.values
.first(where: { $0.userId == mailboxManager.mailbox.userId }) {
AccountManager.instance.switchAccount(newAccount: switchedAccount)
AccountManager.instance.switchMailbox(newMailbox: mailbox)
accountManager.switchAccount(newAccount: switchedAccount)
accountManager.switchMailbox(newMailbox: mailbox)
}
} else {
AccountManager.instance.switchMailbox(newMailbox: mailbox)
accountManager.switchMailbox(newMailbox: mailbox)
}
}

Expand Down
4 changes: 3 additions & 1 deletion Mail/Helpers/WorkInProgress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@

import Foundation
import InfomaniakCoreUI
import InfomaniakDI
import MailCore
import MailResources

// To delete: alert to facilitate tests for beta version
@MainActor
func showWorkInProgressSnackBar() {
IKSnackBar.showSnackBar(message: MailResourcesStrings.Localizable.workInProgressTitle)
@InjectService var snackbarPresenter: SnackBarPresentable
snackbarPresenter.show(message: MailResourcesStrings.Localizable.workInProgressTitle)
}
64 changes: 4 additions & 60 deletions Mail/MailApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,67 +28,13 @@ import Sentry
import SwiftUI
import UIKit

public struct EarlyDIHook {
public init() {
// setup DI and logging ASAP
Logging.initLogging()
setupDI()
}

func setupDI() {
let networkLoginService = Factory(type: InfomaniakNetworkLoginable.self) { _, _ in
InfomaniakNetworkLogin(clientId: MailApiFetcher.clientId)
}
let loginService = Factory(type: InfomaniakLoginable.self) { _, _ in
InfomaniakLogin(clientId: MailApiFetcher.clientId)
}
let keychainHelper = Factory(type: KeychainHelper.self) { _, _ in
KeychainHelper(accessGroup: AccountManager.accessGroup)
}
let tokenStore = Factory(type: TokenStore.self) { _, _ in
TokenStore()
}
let notificationService = Factory(type: InfomaniakNotifications.self) { _, _ in
InfomaniakNotifications(appGroup: AccountManager.appGroup)
}
let appLockHelper = Factory(type: AppLockHelper.self) { _, _ in
AppLockHelper()
}
let bugTracker = Factory(type: BugTracker.self) { _, _ in
BugTracker(info: BugTrackerInfo(project: "app-mobile-mail", gitHubRepoName: "ios-mail", appReleaseType: .beta))
}
let matomoUtils = Factory(type: MatomoUtils.self) { _, _ in
MatomoUtils(siteId: Constants.matomoId, baseURL: URLConstants.matomo.url)
}
let avoider = Factory(type: SnackBarAvoider.self) { _, _ in
SnackBarAvoider()
}
let draftManager = Factory(type: DraftManager.self) { _, _ in
DraftManager()
}
let userActivityController = Factory(type: UserActivityController.self) { _, _ in
UserActivityController()
}

SimpleResolver.sharedResolver.store(factory: networkLoginService)
SimpleResolver.sharedResolver.store(factory: loginService)
SimpleResolver.sharedResolver.store(factory: notificationService)
SimpleResolver.sharedResolver.store(factory: keychainHelper)
SimpleResolver.sharedResolver.store(factory: tokenStore)
SimpleResolver.sharedResolver.store(factory: appLockHelper)
SimpleResolver.sharedResolver.store(factory: bugTracker)
SimpleResolver.sharedResolver.store(factory: matomoUtils)
SimpleResolver.sharedResolver.store(factory: avoider)
SimpleResolver.sharedResolver.store(factory: draftManager)
SimpleResolver.sharedResolver.store(factory: userActivityController)
}
}

@main
struct MailApp: App {
/// Making sure the DI is registered at a very early stage of the app launch.
private let dependencyInjectionHook = EarlyDIHook()
@LazyInjectService var appLockHelper: AppLockHelper

@LazyInjectService private var appLockHelper: AppLockHelper
@LazyInjectService private var accountManager: AccountManager

@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate

Expand All @@ -99,8 +45,6 @@ struct MailApp: App {

@StateObject private var navigationState = NavigationState()

private let accountManager = AccountManager.instance

init() {
DDLogInfo("Application starting in foreground ? \(UIApplication.shared.applicationState != .background)")
ApiFetcher.decoder.dateDecodingStrategy = .iso8601
Expand Down Expand Up @@ -161,7 +105,7 @@ struct MailApp: App {
try await accountManager.updateUser(for: account)
accountManager.enableBugTrackerIfAvailable()

try await accountManager.currentMailboxManager?.contactManager.fetchContactsAndAddressBooks()
try await accountManager.currentContactManager?.fetchContactsAndAddressBooks()
} catch {
DDLogError("Error while updating user account: \(error)")
}
Expand Down
Loading
Loading