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

feat: SwiftUI app #838

Merged
merged 25 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3a9ba3e
feat(SwiftUI App Lifecycle): Basic conversion
PhilippeWeidmann Jun 16, 2023
cb02846
feat(SwiftUI App Lifecycle): Account Switch / Theme / Accent / AppDel…
PhilippeWeidmann Jun 19, 2023
dca5b7f
feat(SwiftUI App Lifecycle): RootView switching
PhilippeWeidmann Jun 23, 2023
8e60909
feat(SwiftUI App Lifecycle): Account switching
PhilippeWeidmann Jun 23, 2023
b7b455b
feat(SwiftUI App Lifecycle): ThreadListManagerView - Ensure ViewModel…
PhilippeWeidmann Jun 23, 2023
e9e8016
Merge branch 'master' into swiftui-app
PhilippeWeidmann Jun 28, 2023
6a4dca2
feat(SwiftUI App Lifecycle): MenuDrawerView - Ensure FolderListView u…
PhilippeWeidmann Jun 28, 2023
a2af080
feat(SwiftUI App Lifecycle): Remove @Environment(\.window)
PhilippeWeidmann Jun 29, 2023
48bbde0
feat(SwiftUI App Lifecycle): NavigationDrawer stop using @Environment…
PhilippeWeidmann Jun 29, 2023
cd00a9c
feat(SwiftUI App Lifecycle): Handle mailto urls
PhilippeWeidmann Jun 29, 2023
a8d7b72
feat(SwiftUI App Lifecycle): NoMailboxView stop using SceneDelegate
PhilippeWeidmann Jun 29, 2023
abf4794
feat(SwiftUI App Lifecycle): Remove SceneDelegate
PhilippeWeidmann Jun 29, 2023
91c3404
feat(SwiftUI App Lifecycle): Make SonarCloud happy
PhilippeWeidmann Jun 29, 2023
b48038f
feat(SwiftUI App Lifecycle): Use NavigationStore for editedDraft
PhilippeWeidmann Jun 30, 2023
5c5b1ce
feat(SwiftUI App Lifecycle): Same navigation for SearchView and Threa…
PhilippeWeidmann Jun 30, 2023
9ab8919
feat(SwiftUI App Lifecycle): PR feedback
PhilippeWeidmann Jun 30, 2023
b13fd88
Merge branch 'master' into swiftui-app
PhilippeWeidmann Jul 4, 2023
5ee2dfc
feat(SwiftUI App Lifecycle): Prevent initial lock
PhilippeWeidmann Jul 4, 2023
d93b65f
feat(SwiftUI App Lifecycle): Restore orientation lock behaviour
PhilippeWeidmann Jul 6, 2023
48788aa
Merge branch 'master' into swiftui-app
PhilippeWeidmann Jul 7, 2023
c80acae
feat(SwiftUI App Lifecycle): Rename navigationStore to navigationState
PhilippeWeidmann Jul 7, 2023
4a3209b
feat(SwiftUI App Lifecycle): AccountView remove duplicated view after…
PhilippeWeidmann Jul 7, 2023
b889e62
feat(SwiftUI App Lifecycle): NoMailboxView | UnavailableMailboxesView
PhilippeWeidmann Jul 7, 2023
b1ce193
feat(SwiftUI App Lifecycle): Fix disconnect crash
PhilippeWeidmann Jul 7, 2023
c2477b4
Merge branch 'master' into swiftui-app
PhilippeWeidmann Jul 7, 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
87 changes: 6 additions & 81 deletions Mail/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,19 @@
*/

import CocoaLumberjackSwift
import InfomaniakBugTracker
import InfomaniakCore
import InfomaniakCoreUI
import InfomaniakDI
import InfomaniakLogin
import InfomaniakNotifications
import MailCore
import Sentry
import SwiftUI
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
private let notificationCenterDelegate = NotificationCenterDelegate()
private var accountManager: AccountManager!
static var orientationLock = UIInterfaceOrientationMask.all

func application(_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
Logging.initLogging()
setupDI()
DDLogInfo("Application starting in foreground ? \(UIApplication.shared.applicationState != .background)")
accountManager = AccountManager.instance
ApiFetcher.decoder.dateDecodingStrategy = .iso8601

func application(
_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
UNUserNotificationCenter.current().delegate = notificationCenterDelegate
Task {
// Ask permission app launch
Expand All @@ -52,17 +40,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return true
}

func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
@InjectService var notificationService: InfomaniakNotifications
for account in accountManager.accounts {
for account in AccountManager.instance.accounts {
guard account.token != nil else { continue }
let userApiFetcher = accountManager.getApiFetcher(for: account.userId, token: account.token)
let userApiFetcher = AccountManager.instance.getApiFetcher(for: account.userId, token: account.token)
Task {
await notificationService.updateRemoteNotificationsTokenIfNeeded(tokenData: deviceToken,
userApiFetcher: userApiFetcher)
Expand All @@ -74,65 +56,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
DDLogError("Failed registering for notifications: \(error)")
}

// MARK: UISceneSession Lifecycle

func application(_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after
// application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}

func application(_ application: UIApplication,
supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return AppDelegate.orientationLock
}

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 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()
}

SimpleResolver.sharedResolver.store(factory: networkLoginService)
SimpleResolver.sharedResolver.store(factory: loginService)
SimpleResolver.sharedResolver.store(factory: notificationService)
SimpleResolver.sharedResolver.store(factory: keychainHelper)
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)
}
}
9 changes: 1 addition & 8 deletions Mail/Components/MailboxListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import RealmSwift
import SwiftUI

struct MailboxListView: View {
@Environment(\.window) private var window

@AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor

@ObservedResults(
Expand All @@ -48,12 +46,7 @@ struct MailboxListView: View {
Spacer()

NavigationLink {
AddMailboxView { mailbox in
DispatchQueue.main.async {
guard let mailbox = mailbox else { return }
(window?.windowScene?.delegate as? SceneDelegate)?.switchMailbox(mailbox)
}
}
AddMailboxView()
} label: {
MailResourcesAsset.addCircle.swiftUIImage
.resizable()
Expand Down
2 changes: 0 additions & 2 deletions Mail/Components/UnavailableMailboxListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import RealmSwift
import SwiftUI

struct UnavailableMailboxListView: View {
@Environment(\.window) private var window

@AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor

@ObservedResults(
Expand Down
21 changes: 0 additions & 21 deletions Mail/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,6 @@
<string>To be able to lock the app</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Atlantis would use Bonjour Service to discover Proxyman app from your local network.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
Expand All @@ -82,8 +63,6 @@
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
Expand Down
8 changes: 6 additions & 2 deletions Mail/LockedAppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import SwiftUI

struct LockedAppView: View {
@LazyInjectService var appLockHelper: AppLockHelper
@Environment(\.window) var window

@EnvironmentObject var navigationState: NavigationState

var body: some View {
ZStack {
Expand Down Expand Up @@ -61,7 +62,10 @@ struct LockedAppView: View {
private func unlockApp() {
Task {
if (try? await appLockHelper.evaluatePolicy(reason: MailResourcesStrings.Localizable.lockAppTitle)) == true {
await (window?.windowScene?.delegate as? SceneDelegate)?.showMainView()
appLockHelper.setTime()
Task {
navigationState.transitionToRootViewDestination(.mainView)
}
}
}
}
Expand Down
162 changes: 162 additions & 0 deletions Mail/MailApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
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 CocoaLumberjackSwift
import InfomaniakBugTracker
import InfomaniakCore
import InfomaniakCoreUI
import InfomaniakDI
import InfomaniakLogin
import InfomaniakNotifications
import MailCore
import Sentry
import SwiftUI
import UIKit

public struct EarlyDIHook {
public init() {
// setup DI ASAP
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 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()
}

SimpleResolver.sharedResolver.store(factory: networkLoginService)
SimpleResolver.sharedResolver.store(factory: loginService)
SimpleResolver.sharedResolver.store(factory: notificationService)
SimpleResolver.sharedResolver.store(factory: keychainHelper)
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)
}
}

@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

@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate

@Environment(\.scenePhase) private var scenePhase

@AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor
@AppStorage(UserDefaults.shared.key(.theme)) private var theme = DefaultPreferences.theme

@StateObject private var navigationState = NavigationState()

private let accountManager = AccountManager.instance

init() {
Logging.initLogging()
DDLogInfo("Application starting in foreground ? \(UIApplication.shared.applicationState != .background)")
ApiFetcher.decoder.dateDecodingStrategy = .iso8601
}

var body: some Scene {
WindowGroup {
RootView()
.environmentObject(navigationState)
.onAppear {
updateUI(accent: accentColor, theme: theme)
}
.onChange(of: theme) { newTheme in
updateUI(accent: accentColor, theme: newTheme)
}
.onChange(of: accentColor) { newAccentColor in
updateUI(accent: newAccentColor, theme: theme)
}
.onChange(of: scenePhase) { newScenePhase in
switch newScenePhase {
case .active:
refreshCacheData()
navigationState.transitionToLockViewIfNeeded()
case .background:
if UserDefaults.shared.isAppLockEnabled && navigationState.rootViewState != .appLocked {
appLockHelper.setTime()
}
case .inactive:
Task {
await NotificationsHelper.updateUnreadCountBadge()
}
@unknown default:
break
}
}
.onChange(of: accountManager.currentAccount) { _ in
refreshCacheData()
}
}
.defaultAppStorage(.shared)
}

func updateUI(accent: AccentColor, theme: Theme) {
let allWindows = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.flatMap { $0.windows }
for window in allWindows {
window.tintColor = accent.primary.color
window.overrideUserInterfaceStyle = theme.interfaceStyle
}
}

func refreshCacheData() {
guard let currentAccount = accountManager.currentAccount else {
return
}

Task {
do {
try await accountManager.updateUser(for: currentAccount)
accountManager.enableBugTrackerIfAvailable()

try await accountManager.currentContactManager?.fetchContactsAndAddressBooks()
} catch {
DDLogError("Error while updating user account: \(error)")
}
}
}
}
5 changes: 3 additions & 2 deletions Mail/NotificationCenterDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ class NotificationCenterDelegate: NSObject, UNUserNotificationCenterDelegate {
if AccountManager.instance.currentAccount.userId != mailboxManager.mailbox.userId {
if let switchedAccount = AccountManager.instance.accounts
.first(where: { $0.userId == mailboxManager.mailbox.userId }) {
(scene?.delegate as? SceneDelegate)?.switchAccount(switchedAccount, mailbox: mailbox)
AccountManager.instance.switchAccount(newAccount: switchedAccount)
AccountManager.instance.switchMailbox(newMailbox: mailbox)
}
} else {
(scene?.delegate as? SceneDelegate)?.switchMailbox(mailbox)
AccountManager.instance.switchMailbox(newMailbox: mailbox)
}
}

Expand Down
Loading