Skip to content

Commit

Permalink
Merge pull request #1130 from Infomaniak/reduceHang
Browse files Browse the repository at this point in the history
fix: Reduce hangs
  • Loading branch information
PhilippeWeidmann authored Nov 22, 2023
2 parents 35824de + 8b7583f commit 179588c
Show file tree
Hide file tree
Showing 19 changed files with 383 additions and 80 deletions.
32 changes: 28 additions & 4 deletions Mail/Components/AvatarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,40 @@ import MailResources
import NukeUI
import SwiftUI

struct AvatarView: View, Equatable {
extension AvatarView: Equatable {
static func == (lhs: AvatarView, rhs: AvatarView) -> Bool {
return lhs.mailboxManager == rhs.mailboxManager
&& lhs.size == rhs.size
&& lhs.contactConfiguration.id == rhs.contactConfiguration.id
}
}

/// A view that displays an avatar linked to a Contact.
struct AvatarView: View {
/// A view model for async loading of contacts
@ObservedObject private var viewModel: AvatarViewModel

/// Optional as this view can be displayed from a context without a mailboxManager available
let mailboxManager: MailboxManager?
private let mailboxManager: MailboxManager?

let displayablePerson: CommonContact
/// The size of the avatar view
private let size: CGFloat

var size: CGFloat = 28
/// The configuration associated to this view
private let contactConfiguration: ContactConfiguration

init(mailboxManager: MailboxManager?, contactConfiguration: ContactConfiguration, size: CGFloat = 28) {
self.mailboxManager = mailboxManager
self.size = size
self.contactConfiguration = contactConfiguration

// We use an ObservedObject instead of a StateObject because SwiftUI doesn't want to respect Equatable
_viewModel = ObservedObject(wrappedValue: AvatarViewModel(contactConfiguration: contactConfiguration))
}

var body: some View {
Group {
let displayablePerson = viewModel.displayablePerson
if let mailboxManager,
let currentToken = mailboxManager.apiFetcher.currentToken,
let avatarImageRequest = displayablePerson.avatarImageRequest.authenticatedRequestIfNeeded(token:
Expand Down
6 changes: 2 additions & 4 deletions Mail/Components/Buttons/AccountButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,8 @@ struct AccountButton: View {
presentedCurrentAccount = mailboxManager.account
} label: {
if let currentAccountUser = mailboxManager.account.user {
AvatarView(
mailboxManager: mailboxManager,
displayablePerson: CommonContact(user: currentAccountUser)
)
AvatarView(mailboxManager: mailboxManager,
contactConfiguration: .user(user: currentAccountUser))
}
}
.sheet(item: $presentedCurrentAccount) { account in
Expand Down
5 changes: 1 addition & 4 deletions Mail/Components/RecipientCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,7 @@ struct RecipientCell: View {
HStack(spacing: UIPadding.small) {
AvatarView(
mailboxManager: mailboxManager,
displayablePerson: CommonContact(
recipient: recipient,
contextMailboxManager: mailboxManager
),
contactConfiguration: .recipient(recipient: recipient, contextMailboxManager: mailboxManager),
size: 40
)
.accessibilityHidden(true)
Expand Down
8 changes: 4 additions & 4 deletions Mail/Components/ThreadCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ struct ThreadCellDataHolder {
}
}

func commonContact(contextMailboxManager: MailboxManager) -> CommonContact {
func contactConfiguration(contextMailboxManager: MailboxManager) -> ContactConfiguration {
if let recipientToDisplay {
return CommonContact(recipient: recipientToDisplay, contextMailboxManager: contextMailboxManager)
return .recipient(recipient: recipientToDisplay, contextMailboxManager: contextMailboxManager)
} else {
return CommonContact.emptyContact(contextMailboxManager: contextMailboxManager)
return .emptyContact
}
}
}
Expand Down Expand Up @@ -149,7 +149,7 @@ struct ThreadCell: View {
ZStack {
AvatarView(
mailboxManager: mailboxManager,
displayablePerson: dataHolder.commonContact(contextMailboxManager: mailboxManager),
contactConfiguration: dataHolder.contactConfiguration(contextMailboxManager: mailboxManager),
size: 40
)
.opacity(isSelected ? 0 : 1)
Expand Down
8 changes: 8 additions & 0 deletions Mail/Helpers/AppAssembly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ enum ApplicationAssembly {
},
Factory(type: AppLaunchCounter.self) { _, _ in
AppLaunchCounter()
},
Factory(type: ContactCache.self) { _, _ in
let contactCache = ContactCache()
if Bundle.main.isExtension {
// Limit the cache size in extension mode, not strictly needed, but coherent.
contactCache.countLimit = Constants.contactCacheExtensionMaxCount
}
return contactCache
}
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct ContactActionsHeaderView: View {

var body: some View {
HStack {
AvatarView(mailboxManager: mailboxManager, displayablePerson: displayablePerson, size: 40)
AvatarView(mailboxManager: mailboxManager, contactConfiguration: .contact(contact: displayablePerson), size: 40)
.accessibilityHidden(true)
VStack(alignment: .leading) {
Text(displayablePerson, format: .displayablePerson())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,9 @@ struct ContactActionsView: View {

var body: some View {
VStack(alignment: .leading, spacing: UIPadding.small) {
ContactActionsHeaderView(displayablePerson: CommonContact(
recipient: recipient,
contextMailboxManager: mailboxManager
))
let contactConfiguration = ContactConfiguration.recipient(recipient: recipient, contextMailboxManager: mailboxManager)
let contact = CommonContactCache.getOrCreateContact(contactConfiguration: contactConfiguration)
ContactActionsHeaderView(displayablePerson: contact)

VStack(alignment: .leading, spacing: 0) {
ForEach(actions) { action in
Expand Down
3 changes: 1 addition & 2 deletions Mail/Views/Switch User/AccountCellView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ struct AccountHeaderCell: View {

var body: some View {
HStack(spacing: UIPadding.small) {
AvatarView(mailboxManager: mailboxManager, displayablePerson: CommonContact(user: account.user), size: 40)

AvatarView(mailboxManager: mailboxManager, contactConfiguration: .user(user: account.user), size: 40)
VStack(alignment: .leading, spacing: 0) {
Text(account.user.displayName)
.textStyle(.bodyMedium)
Expand Down
34 changes: 18 additions & 16 deletions Mail/Views/Switch User/AccountView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,25 @@ struct AccountView: View {
var body: some View {
VStack(spacing: 0) {
ScrollView {
AvatarView(mailboxManager: mailboxManager,
displayablePerson: CommonContact(user: account.user),
size: AccountView.avatarViewSize)
.padding(.bottom, value: .regular)
.padding(.top, value: .medium)
.background {
if EasterEgg.halloween.shouldTrigger() {
LottieView(configuration: LottieConfiguration(id: 1, filename: "illu_easter_egg_halloween"),
isVisible: $isLottieAnimationVisible)
.offset(y: AccountView.avatarViewSize)
.allowsHitTesting(false)
.onAppear {
EasterEgg.halloween.onTrigger()
}
}
AvatarView(
mailboxManager: mailboxManager,
contactConfiguration: .user(user: account.user),
size: AccountView.avatarViewSize
)
.padding(.bottom, value: .regular)
.padding(.top, value: .medium)
.background {
if EasterEgg.halloween.shouldTrigger() {
LottieView(configuration: LottieConfiguration(id: 1, filename: "illu_easter_egg_halloween"),
isVisible: $isLottieAnimationVisible)
.offset(y: AccountView.avatarViewSize)
.allowsHitTesting(false)
.onAppear {
EasterEgg.halloween.onTrigger()
}
}
.zIndex(1)
}
.zIndex(1)

VStack(spacing: 0) {
Text(account.user.displayName)
Expand Down
23 changes: 17 additions & 6 deletions Mail/Views/Thread/MessageHeaderSummaryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,8 @@ struct MessageHeaderSummaryView: View {
} label: {
AvatarView(
mailboxManager: mailboxManager,
displayablePerson: CommonContact(
recipient: recipient,
contextMailboxManager: mailboxManager
),
contactConfiguration: .recipient(recipient: recipient,
contextMailboxManager: mailboxManager),
size: 40
)
}
Expand All @@ -70,7 +68,14 @@ struct MessageHeaderSummaryView: View {
HStack(alignment: .firstTextBaseline, spacing: UIPadding.small) {
VStack {
ForEach(message.from) { recipient in
Text(CommonContact(recipient: recipient, contextMailboxManager: mailboxManager),
let contactConfiguration = ContactConfiguration.recipient(
recipient: recipient,
contextMailboxManager: mailboxManager
)
let contact = CommonContactCache
.getOrCreateContact(contactConfiguration: contactConfiguration)

Text(contact,
format: .displayablePerson())
.lineLimit(1)
.textStyle(.bodyMedium)
Expand All @@ -87,7 +92,13 @@ struct MessageHeaderSummaryView: View {
HStack {
Text(
message.recipients.map {
CommonContact(recipient: $0, contextMailboxManager: mailboxManager).formatted()
let contactConfiguration = ContactConfiguration.recipient(
recipient: $0,
contextMailboxManager: mailboxManager
)
let contact = CommonContactCache
.getOrCreateContact(contactConfiguration: contactConfiguration)
return contact.formatted()
},
format: .list(type: .and)
)
Expand Down
45 changes: 45 additions & 0 deletions MailCore/Models/Contact/AvatarImageRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
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
import Nuke

public struct AvatarImageRequest {
let imageRequest: ImageRequest?
let shouldAuthenticate: Bool

public func authenticatedRequestIfNeeded(token: ApiToken) -> ImageRequest? {
guard let unauthenticatedImageRequest = imageRequest,
let unauthenticatedUrlRequest = unauthenticatedImageRequest.urlRequest else {
return nil
}

guard shouldAuthenticate else {
return unauthenticatedImageRequest
}

var authenticatedUrlRequest = unauthenticatedUrlRequest
authenticatedUrlRequest.addValue(
"Bearer \(token.accessToken)",
forHTTPHeaderField: "Authorization"
)

return ImageRequest(urlRequest: authenticatedUrlRequest)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,44 @@ import MailResources
import Nuke
import UIKit

public struct AvatarImageRequest {
let imageRequest: ImageRequest?
let shouldAuthenticate: Bool
public final class CommonContact: Identifiable {
/// Empty contact is a singleton
public static let emptyContact = CommonContact()

public func authenticatedRequestIfNeeded(token: ApiToken) -> ImageRequest? {
guard let unauthenticatedImageRequest = imageRequest,
let unauthenticatedUrlRequest = unauthenticatedImageRequest.urlRequest else {
return nil
}

guard shouldAuthenticate else {
return unauthenticatedImageRequest
}

var authenticatedUrlRequest = unauthenticatedUrlRequest
authenticatedUrlRequest.addValue(
"Bearer \(token.accessToken)",
forHTTPHeaderField: "Authorization"
)
public let id: Int

return ImageRequest(urlRequest: authenticatedUrlRequest)
}
}

public struct CommonContact {
public let fullName: String
public let email: String
public let avatarImageRequest: AvatarImageRequest
public let color: UIColor

public init(recipient: Recipient, contextMailboxManager: MailboxManager) {
static func from(contactConfiguration: ContactConfiguration) -> CommonContact {
switch contactConfiguration {
case .recipient(let recipient, let contextMailboxManager):
CommonContact(recipient: recipient, contextMailboxManager: contextMailboxManager)
case .user(let user):
CommonContact(user: user)
case .contact(let contact):
contact
case .emptyContact:
emptyContact
}
}

/// Empty contact
private init() {
let recipient = Recipient(email: "", name: "")
email = recipient.email
fullName = recipient.name
id = recipient.id.hashValue
color = UIColor.backgroundColor(from: recipient.hash, with: UIConstants.avatarColors)
avatarImageRequest = AvatarImageRequest(imageRequest: nil, shouldAuthenticate: true)
}

/// Init form a `Recipient` in the context of a mailbox
init(recipient: Recipient, contextMailboxManager: MailboxManager) {
email = recipient.email
id = recipient.id.hashValue

if recipient.isMe(currentMailboxEmail: contextMailboxManager.mailbox.email) {
fullName = MailResourcesStrings.Localizable.contactMe
Expand All @@ -65,15 +71,17 @@ public struct CommonContact {
avatarImageRequest = AvatarImageRequest(imageRequest: nil, shouldAuthenticate: false)
}
} else {
let mainViewRealm = contextMailboxManager.contactManager.viewRealm
let mainViewRealm = contextMailboxManager.contactManager.getRealm()
let contact = contextMailboxManager.contactManager.getContact(for: recipient, realm: mainViewRealm)
fullName = contact?.name ?? (recipient.name.isEmpty ? recipient.email : recipient.name)
color = contact?.color ?? UIColor.backgroundColor(from: email.hash, with: UIConstants.avatarColors)
avatarImageRequest = AvatarImageRequest(imageRequest: contact?.avatarImageRequest, shouldAuthenticate: true)
}
}

public init(user: UserProfile) {
/// Init form a `UserProfile`
init(user: UserProfile) {
id = user.id
fullName = user.displayName
email = user.email
color = UIColor.backgroundColor(from: user.id, with: UIConstants.avatarColors)
Expand All @@ -83,11 +91,6 @@ public struct CommonContact {
avatarImageRequest = AvatarImageRequest(imageRequest: nil, shouldAuthenticate: false)
}
}

/// A `CommonContact` that represents no-one.
public static func emptyContact(contextMailboxManager: MailboxManager) -> CommonContact {
CommonContact(recipient: Recipient(email: "", name: ""), contextMailboxManager: contextMailboxManager)
}
}

extension CommonContact: Equatable {
Expand Down
Loading

0 comments on commit 179588c

Please sign in to comment.