From f9477ae3a446a6690dca4f29995fd61640700965 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 28 Mar 2023 09:52:01 +0200 Subject: [PATCH 1/6] chore: Upgrade nuke --- .package.resolved | 4 ++-- MailCore/Cache/AccountManager.swift | 2 +- MailCore/Models/MergedContact.swift | 2 +- MailCore/Utils/Nuke+Authorization.swift | 7 ++++--- Project.swift | 3 ++- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.package.resolved b/.package.resolved index f48c8aa7e..fe0d9aa68 100644 --- a/.package.resolved +++ b/.package.resolved @@ -139,8 +139,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kean/Nuke", "state" : { - "revision" : "33f7e93be5d4ec027d42af77a8ec4680d1862ad2", - "version" : "11.6.4" + "revision" : "f4d9b95788679d0654c032961f73e7e9c16ca6b4", + "version" : "12.1.0" } }, { diff --git a/MailCore/Cache/AccountManager.swift b/MailCore/Cache/AccountManager.swift index 272d26398..328de508a 100644 --- a/MailCore/Cache/AccountManager.swift +++ b/MailCore/Cache/AccountManager.swift @@ -72,7 +72,7 @@ public extension InfomaniakUser { var avatarImage: Image { get async { if let avatarURL = URL(string: avatar), - let avatarImage = try? await ImagePipeline.shared.image(for: avatarURL).image { + let avatarImage = try? await ImagePipeline.shared.image(for: avatarURL) { return Image(uiImage: avatarImage) } else { let backgroundColor = UIColor.backgroundColor(from: id) diff --git a/MailCore/Models/MergedContact.swift b/MailCore/Models/MergedContact.swift index a50c8b025..df62019ce 100644 --- a/MailCore/Models/MergedContact.swift +++ b/MailCore/Models/MergedContact.swift @@ -72,7 +72,7 @@ public class MergedContact { if let localImage = local?.image { return Image(uiImage: localImage) } else if let avatarPath = remote?.avatar, - let avatarUIImage = try? await ImagePipeline.shared.imageWithAuthentication(for: Endpoint.resource(avatarPath).url).image { + let avatarUIImage = try? await ImagePipeline.shared.imageWithAuthentication(for: Endpoint.resource(avatarPath).url) { return Image(uiImage: avatarUIImage) } diff --git a/MailCore/Utils/Nuke+Authorization.swift b/MailCore/Utils/Nuke+Authorization.swift index 20f6c9d5c..14239c1d3 100644 --- a/MailCore/Utils/Nuke+Authorization.swift +++ b/MailCore/Utils/Nuke+Authorization.swift @@ -18,15 +18,16 @@ import Foundation import Nuke +import UIKit -extension ImagePipeline { - public func imageWithAuthentication(for url: URL, delegate: (any ImageTaskDelegate)? = nil) async throws -> ImageResponse { +public extension ImagePipeline { + func imageWithAuthentication(for url: URL) async throws -> UIImage { var urlRequest = URLRequest(url: url) urlRequest.addValue( "Bearer \(AccountManager.instance.currentAccount.token.accessToken)", forHTTPHeaderField: "Authorization" ) - return try await image(for: ImageRequest(urlRequest: urlRequest), delegate: delegate) + return try await image(for: ImageRequest(urlRequest: urlRequest)) } } diff --git a/Project.swift b/Project.swift index 679d0ad54..c1664dc30 100644 --- a/Project.swift +++ b/Project.swift @@ -45,7 +45,7 @@ let project = Project(name: "Mail", .package(url: "https://github.com/markiv/SwiftUI-Shimmer", .upToNextMajor(from: "1.0.1")), .package(url: "https://github.com/dkk/WrappingHStack", .upToNextMajor(from: "2.0.0")), .package(url: "https://github.com/SCENEE/FloatingPanel", .upToNextMajor(from: "2.0.0")), - .package(url: "https://github.com/kean/Nuke", .upToNextMajor(from: "11.3.0")), + .package(url: "https://github.com/kean/Nuke", .upToNextMajor(from: "12.0.0")), .package(url: "https://github.com/airbnb/lottie-ios", .exact("3.5.0")), .package(url: "https://github.com/scinfu/SwiftSoup", .upToNextMajor(from: "2.5.3")) ], @@ -164,6 +164,7 @@ let project = Project(name: "Mail", .package(product: "RealmSwift"), .package(product: "SwiftRegex"), .package(product: "Nuke"), + .package(product: "NukeUI"), .package(product: "SwiftSoup") ], settings: .settings(base: baseSettings) From 632e0cb71b508324bf69f2389924e1ce447d74b6 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 28 Mar 2023 10:07:50 +0200 Subject: [PATCH 2/6] feat: AvatarDisplayable + AvatarView --- Mail/Components/ContactImage.swift | 16 ++++---- Mail/Components/UserAvatarView.swift | 45 +++++++++++++++++++++ Mail/Views/Thread List/ThreadListView.swift | 11 +---- MailCore/API/MailApiFetcher+Nuke.swift | 40 ++++++++++++++++++ MailCore/Utils/AvatarDisplayable.swift | 43 ++++++++++++++++++++ 5 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 Mail/Components/UserAvatarView.swift create mode 100644 MailCore/API/MailApiFetcher+Nuke.swift create mode 100644 MailCore/Utils/AvatarDisplayable.swift diff --git a/Mail/Components/ContactImage.swift b/Mail/Components/ContactImage.swift index ffcad4f16..93f48ae85 100644 --- a/Mail/Components/ContactImage.swift +++ b/Mail/Components/ContactImage.swift @@ -20,17 +20,15 @@ import MailCore import SwiftUI struct ContactImage: View { - var image: Image - var size: CGFloat + let image: Image + let size: CGFloat var body: some View { - ZStack { - image - .resizable() - .scaledToFit() - .frame(width: size, height: size) - .clipShape(Circle()) - } + image + .resizable() + .scaledToFit() + .frame(width: size, height: size) + .clipShape(Circle()) } } diff --git a/Mail/Components/UserAvatarView.swift b/Mail/Components/UserAvatarView.swift new file mode 100644 index 000000000..256e99db1 --- /dev/null +++ b/Mail/Components/UserAvatarView.swift @@ -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 . + */ + +import InfomaniakCore +import MailCore +import NukeUI +import SwiftUI + +struct AvatarView: View { + let avatarDisplayable: AvatarDisplayable + var size: CGFloat = 28 + + var body: some View { + if let avatarImageRequest = avatarDisplayable.avatarImageRequest { + LazyImage(request: avatarImageRequest) { state in + if let image = state.image { + ContactImage(image: image, size: size) + } else { + InitialsView( + initials: avatarDisplayable.initials, + color: avatarDisplayable.initialsBackgroundColor, + size: size + ) + } + } + } else { + InitialsView(initials: avatarDisplayable.initials, color: avatarDisplayable.initialsBackgroundColor, size: size) + } + } +} diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index bf91ad20c..7abff9309 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -352,16 +352,7 @@ private struct ThreadListToolbar: ViewModifier { Button { isShowingSwitchAccount.toggle() } label: { - splitViewManager.avatarImage - .resizable() - .scaledToFit() - .frame(width: 28, height: 28) - .clipShape(Circle()) - } - .task { - if let account = AccountManager.instance.currentAccount { - splitViewManager.avatarImage = await account.user.avatarImage - } + AvatarView(avatarDisplayable: AccountManager.instance.currentAccount.user) } } } diff --git a/MailCore/API/MailApiFetcher+Nuke.swift b/MailCore/API/MailApiFetcher+Nuke.swift new file mode 100644 index 000000000..e81219c59 --- /dev/null +++ b/MailCore/API/MailApiFetcher+Nuke.swift @@ -0,0 +1,40 @@ +/* + 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 Foundation +import InfomaniakCore +import Nuke + +public extension MailApiFetcher { + func avatarImageRequestForContact(_ contact: MergedContact) -> ImageRequest? { + guard let avatarPath = contact.remote?.avatar else { return nil } + let endpoint = Endpoint.resource(avatarPath) + + return authenticatedImageRequest(endpoint.url) + } + + func authenticatedImageRequest(_ url: URL) -> ImageRequest? { + var urlRequest = URLRequest(url: url) + urlRequest.addValue( + "Bearer \(currentToken?.accessToken ?? "")", + forHTTPHeaderField: "Authorization" + ) + + return ImageRequest(urlRequest: urlRequest) + } +} diff --git a/MailCore/Utils/AvatarDisplayable.swift b/MailCore/Utils/AvatarDisplayable.swift new file mode 100644 index 000000000..bffd8e8ca --- /dev/null +++ b/MailCore/Utils/AvatarDisplayable.swift @@ -0,0 +1,43 @@ +/* + 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 Foundation +import InfomaniakCore +import Nuke +import UIKit + +public protocol AvatarDisplayable { + var avatarImageRequest: ImageRequest? { get } + var initials: String { get } + var initialsBackgroundColor: UIColor { get } +} + +extension UserProfile: AvatarDisplayable { + public var avatarImageRequest: ImageRequest? { + guard let avatarURL = URL(string: avatar) else { return nil } + return ImageRequest(url: avatarURL) + } + + public var initials: String { + displayName.initials + } + + public var initialsBackgroundColor: UIColor { + UIColor.backgroundColor(from: id) + } +} From 6ac037d3424ba3991a8e6c3f84ce7fd68d49defe Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 28 Mar 2023 13:04:18 +0200 Subject: [PATCH 3/6] feat: Shared logic for contact image fetching --- ...{UserAvatarView.swift => AvatarView.swift} | 4 +- .../RecipientAutocompletionCell.swift | 2 +- Mail/Components/RecipientImage.swift | 59 ------------------- Mail/Components/ThreadCell.swift | 2 +- .../Bottom sheets/ContactActionsView.swift | 2 +- Mail/Views/SplitView.swift | 1 - Mail/Views/Switch User/AccountCellView.swift | 11 +--- Mail/Views/Switch User/AccountView.swift | 10 +--- .../Thread/MessageHeaderSummaryView.swift | 2 +- MailCore/Cache/AccountManager.swift | 28 --------- MailCore/Models/MergedContact.swift | 43 ++++++-------- MailCore/Models/Recipient.swift | 34 ++++------- MailCore/Utils/AvatarDisplayable.swift | 6 ++ 13 files changed, 46 insertions(+), 158 deletions(-) rename Mail/Components/{UserAvatarView.swift => AvatarView.swift} (90%) delete mode 100644 Mail/Components/RecipientImage.swift diff --git a/Mail/Components/UserAvatarView.swift b/Mail/Components/AvatarView.swift similarity index 90% rename from Mail/Components/UserAvatarView.swift rename to Mail/Components/AvatarView.swift index 256e99db1..765c6d756 100644 --- a/Mail/Components/UserAvatarView.swift +++ b/Mail/Components/AvatarView.swift @@ -26,7 +26,9 @@ struct AvatarView: View { var size: CGFloat = 28 var body: some View { - if let avatarImageRequest = avatarDisplayable.avatarImageRequest { + if let localImage = avatarDisplayable.localImage { + localImage + } else if let avatarImageRequest = avatarDisplayable.avatarImageRequest { LazyImage(request: avatarImageRequest) { state in if let image = state.image { ContactImage(image: image, size: size) diff --git a/Mail/Components/RecipientAutocompletionCell.swift b/Mail/Components/RecipientAutocompletionCell.swift index da0b0ecc7..7e1f31c68 100644 --- a/Mail/Components/RecipientAutocompletionCell.swift +++ b/Mail/Components/RecipientAutocompletionCell.swift @@ -24,7 +24,7 @@ struct RecipientAutocompletionCell: View { var body: some View { HStack { - RecipientImage(recipient: recipient) + AvatarView(avatarDisplayable: recipient, size: 40) if recipient.name.isEmpty { Text(recipient.email) diff --git a/Mail/Components/RecipientImage.swift b/Mail/Components/RecipientImage.swift deleted file mode 100644 index 9bb0220dd..000000000 --- a/Mail/Components/RecipientImage.swift +++ /dev/null @@ -1,59 +0,0 @@ -/* - 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 MailCore -import MailResources -import SwiftUI - -struct RecipientImage: View { - var recipient: Recipient - var size: CGFloat - - @State private var image: Image? - - init(recipient: Recipient, size: CGFloat = 40) { - self.image = recipient.cachedAvatarImage - self.recipient = recipient - self.size = size - } - - var body: some View { - if let image = image { - ContactImage(image: image, size: size) - } else { - InitialsView(initials: recipient.initials, color: recipient.color, size: size) - .task { - await fetchAvatar() - } - } - } - - func fetchAvatar() async { - if let avatarImage = await recipient.avatarImage { - withAnimation { - image = avatarImage - } - } - } -} - -struct RecipientImage_Previews: PreviewProvider { - static var previews: some View { - RecipientImage(recipient: PreviewHelper.sampleRecipient1) - } -} diff --git a/Mail/Components/ThreadCell.swift b/Mail/Components/ThreadCell.swift index d14be98b7..c783460fe 100644 --- a/Mail/Components/ThreadCell.swift +++ b/Mail/Components/ThreadCell.swift @@ -123,7 +123,7 @@ struct ThreadCell: View { Group { if density == .large, let recipient = dataHolder.recipientToDisplay { ZStack { - RecipientImage(recipient: recipient) + AvatarView(avatarDisplayable: recipient, size: 40) CheckboxView(isSelected: isSelected, density: density) .opacity(isSelected ? 1 : 0) } diff --git a/Mail/Views/Bottom sheets/ContactActionsView.swift b/Mail/Views/Bottom sheets/ContactActionsView.swift index a17ed5c6a..1b2b0a766 100644 --- a/Mail/Views/Bottom sheets/ContactActionsView.swift +++ b/Mail/Views/Bottom sheets/ContactActionsView.swift @@ -64,7 +64,7 @@ struct ContactActionsView: View { var body: some View { VStack(alignment: .leading, spacing: 16) { HStack { - RecipientImage(recipient: recipient, size: 32) + AvatarView(avatarDisplayable: recipient, size: 32) VStack(alignment: .leading) { Text(recipient.contact?.name ?? recipient.title) .textStyle(.bodyMedium) diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index eeb89dc91..90b7cfd26 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -44,7 +44,6 @@ public class SplitViewManager: ObservableObject { @Published var showSearch = false @Published var selectedFolder: Folder? var splitViewController: UISplitViewController? - @Published var avatarImage = MailResourcesAsset.placeholderAvatar.swiftUIImage init(folder: Folder?) { selectedFolder = folder diff --git a/Mail/Views/Switch User/AccountCellView.swift b/Mail/Views/Switch User/AccountCellView.swift index 371e37660..f17409259 100644 --- a/Mail/Views/Switch User/AccountCellView.swift +++ b/Mail/Views/Switch User/AccountCellView.swift @@ -70,15 +70,9 @@ struct AccountHeaderCell: View { let account: Account @Binding var isSelected: Bool - @State private var avatarImage = MailResourcesAsset.placeholderAvatar.swiftUIImage - var body: some View { HStack(spacing: 8) { - avatarImage - .resizable() - .frame(width: 38, height: 38) - .clipShape(Circle()) - + AvatarView(avatarDisplayable: account.user, size: 38) VStack(alignment: .leading, spacing: 2) { Text(account.user.displayName) .textStyle(.bodyMedium) @@ -93,9 +87,6 @@ struct AccountHeaderCell: View { .foregroundColor(.accentColor) } } - .task { - avatarImage = await account.user.avatarImage - } } } diff --git a/Mail/Views/Switch User/AccountView.swift b/Mail/Views/Switch User/AccountView.swift index 90f4eb3b0..a293b4783 100644 --- a/Mail/Views/Switch User/AccountView.swift +++ b/Mail/Views/Switch User/AccountView.swift @@ -51,7 +51,6 @@ struct AccountView: View { @LazyInjectService private var matomo: MatomoUtils - @State private var avatarImage = MailResourcesAsset.placeholderAvatar.swiftUIImage private let account = AccountManager.instance.currentAccount! @State private var isShowingLogoutAlert = false @State private var isShowingDeleteAccount = false @@ -63,11 +62,7 @@ struct AccountView: View { NavigationView { VStack(spacing: 0) { ScrollView { - // Header - avatarImage - .resizable() - .frame(width: 104, height: 104) - .clipShape(Circle()) + AvatarView(avatarDisplayable: account.user, size: 104) .padding(.top, 24) .padding(.bottom, 16) @@ -127,9 +122,6 @@ struct AccountView: View { Label(MailResourcesStrings.Localizable.buttonClose, systemImage: "xmark") }) } - .task { - avatarImage = await account.user.avatarImage - } .sheet(isPresented: $isShowingDeleteAccount) { DeleteAccountView(account: account, delegate: delegate) } diff --git a/Mail/Views/Thread/MessageHeaderSummaryView.swift b/Mail/Views/Thread/MessageHeaderSummaryView.swift index abee155f2..aa4cdf056 100644 --- a/Mail/Views/Thread/MessageHeaderSummaryView.swift +++ b/Mail/Views/Thread/MessageHeaderSummaryView.swift @@ -43,7 +43,7 @@ struct MessageHeaderSummaryView: View { matomo.track(eventWithCategory: .message, name: "selectAvatar") recipientTapped(recipient) } label: { - RecipientImage(recipient: recipient, size: 40) + AvatarView(avatarDisplayable: recipient, size: 40) } } diff --git a/MailCore/Cache/AccountManager.swift b/MailCore/Cache/AccountManager.swift index 328de508a..267790310 100644 --- a/MailCore/Cache/AccountManager.swift +++ b/MailCore/Cache/AccountManager.swift @@ -59,34 +59,6 @@ public extension InfomaniakNetworkLoginable { } } -public extension InfomaniakUser { - var cachedAvatarImage: Image? { - if let avatarURL = URL(string: avatar), - let avatarUIImage = ImagePipeline.shared.cache[avatarURL]?.image { - return Image(uiImage: avatarUIImage) - } - - return nil - } - - var avatarImage: Image { - get async { - if let avatarURL = URL(string: avatar), - let avatarImage = try? await ImagePipeline.shared.image(for: avatarURL) { - return Image(uiImage: avatarImage) - } else { - let backgroundColor = UIColor.backgroundColor(from: id) - let initialsImage = UIImage.getInitialsPlaceholder( - with: displayName, - size: CGSize(width: 40, height: 40), - backgroundColor: backgroundColor - ) - return Image(uiImage: initialsImage) - } - } - } -} - @globalActor actor AccountActor: GlobalActor { static let shared = AccountActor() diff --git a/MailCore/Models/MergedContact.swift b/MailCore/Models/MergedContact.swift index df62019ce..867ba660e 100644 --- a/MailCore/Models/MergedContact.swift +++ b/MailCore/Models/MergedContact.swift @@ -63,37 +63,30 @@ public class MergedContact { return remote != nil } - public var hasAvatar: Bool { - return local?.imageData != nil || remote?.avatar != nil + public init(email: String, remote: Contact?, local: CNContact?) { + self.email = email + self.remote = remote + self.local = local } +} - public var avatarImage: Image? { - get async { - if let localImage = local?.image { - return Image(uiImage: localImage) - } else if let avatarPath = remote?.avatar, - let avatarUIImage = try? await ImagePipeline.shared.imageWithAuthentication(for: Endpoint.resource(avatarPath).url) { - return Image(uiImage: avatarUIImage) - } - - return nil - } +extension MergedContact: AvatarDisplayable { + public var localImage: Image? { + guard let localUIImage = local?.image else { return nil } + return Image(uiImage: localUIImage) } - public var cachedAvatarImage: Image? { - if let localImage = local?.image { - return Image(uiImage: localImage) - } else if let avatarPath = remote?.avatar, - let avatarUIImage = ImagePipeline.shared.cache[Endpoint.resource(avatarPath).url]?.image { - return Image(uiImage: avatarUIImage) - } + public var avatarImageRequest: ImageRequest? { + guard let remoteAvatar = remote?.avatar else { return nil } + let avatarURL = Endpoint.resource(remoteAvatar).url + return AccountManager.instance.currentMailboxManager?.apiFetcher.authenticatedImageRequest(avatarURL) + } - return nil + public var initials: String { + "" } - public init(email: String, remote: Contact?, local: CNContact?) { - self.email = email - self.remote = remote - self.local = local + public var initialsBackgroundColor: UIColor { + color } } diff --git a/MailCore/Models/Recipient.swift b/MailCore/Models/Recipient.swift index 31f9cce15..76510342b 100644 --- a/MailCore/Models/Recipient.swift +++ b/MailCore/Models/Recipient.swift @@ -18,6 +18,7 @@ import Foundation import MailResources +import Nuke import RealmSwift import SwiftUI @@ -107,30 +108,21 @@ public class Recipient: EmbeddedObject, Codable { return "\(name) \(emailString)" } } +} - public var avatarImage: Image? { - get async { - if isCurrentUser { - return await AccountManager.instance.currentAccount.user.avatarImage - } else if let contact = contact, - contact.hasAvatar, - let avatarImage = await contact.avatarImage { - return avatarImage - } else { - return nil - } - } +extension Recipient: AvatarDisplayable { + public var localImage: Image? { + contact?.localImage } - public var cachedAvatarImage: Image? { - if isCurrentUser { - return AccountManager.instance.currentAccount.user.cachedAvatarImage - } else if let contact = contact, - contact.hasAvatar, - let avatarImage = contact.cachedAvatarImage { - return avatarImage - } else { - return nil + public var avatarImageRequest: ImageRequest? { + guard !isCurrentUser else { + return AccountManager.instance.currentAccount.user.avatarImageRequest } + return contact?.avatarImageRequest + } + + public var initialsBackgroundColor: UIColor { + color } } diff --git a/MailCore/Utils/AvatarDisplayable.swift b/MailCore/Utils/AvatarDisplayable.swift index bffd8e8ca..06779f857 100644 --- a/MailCore/Utils/AvatarDisplayable.swift +++ b/MailCore/Utils/AvatarDisplayable.swift @@ -19,15 +19,21 @@ import Foundation import InfomaniakCore import Nuke +import SwiftUI import UIKit public protocol AvatarDisplayable { + var localImage: Image? { get } var avatarImageRequest: ImageRequest? { get } var initials: String { get } var initialsBackgroundColor: UIColor { get } } extension UserProfile: AvatarDisplayable { + public var localImage: Image? { + nil + } + public var avatarImageRequest: ImageRequest? { guard let avatarURL = URL(string: avatar) else { return nil } return ImageRequest(url: avatarURL) From db311a47fc92f0a6e0f142ffecb6346805c27a67 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 28 Mar 2023 14:34:12 +0200 Subject: [PATCH 4/6] feat: Single cache source for all avatars --- Mail/Components/AvatarView.swift | 4 +-- MailCore/Models/MergedContact.swift | 37 ++++++++++++++++-------- MailCore/Models/Recipient.swift | 4 --- MailCore/Utils/AvatarDisplayable.swift | 5 ---- MailCore/Utils/LocalContactsHelper.swift | 9 +++++- 5 files changed, 34 insertions(+), 25 deletions(-) diff --git a/Mail/Components/AvatarView.swift b/Mail/Components/AvatarView.swift index 765c6d756..256e99db1 100644 --- a/Mail/Components/AvatarView.swift +++ b/Mail/Components/AvatarView.swift @@ -26,9 +26,7 @@ struct AvatarView: View { var size: CGFloat = 28 var body: some View { - if let localImage = avatarDisplayable.localImage { - localImage - } else if let avatarImageRequest = avatarDisplayable.avatarImageRequest { + if let avatarImageRequest = avatarDisplayable.avatarImageRequest { LazyImage(request: avatarImageRequest) { state in if let image = state.image { ContactImage(image: image, size: size) diff --git a/MailCore/Models/MergedContact.swift b/MailCore/Models/MergedContact.swift index 867ba660e..16b2fcc71 100644 --- a/MailCore/Models/MergedContact.swift +++ b/MailCore/Models/MergedContact.swift @@ -25,10 +25,13 @@ import SwiftUI import UIKit extension CNContact { - var image: UIImage? { - guard let imageData = imageData else { return nil } - let localImage = UIImage(data: imageData) - return localImage + func pngImageData() async -> Data? { + // We have to load something that Nuke can cache + guard let imageData, + let convertedImage = UIImage(data: imageData)?.pngData() else { + return nil + } + return convertedImage } } @@ -71,15 +74,25 @@ public class MergedContact { } extension MergedContact: AvatarDisplayable { - public var localImage: Image? { - guard let localUIImage = local?.image else { return nil } - return Image(uiImage: localUIImage) - } - public var avatarImageRequest: ImageRequest? { - guard let remoteAvatar = remote?.avatar else { return nil } - let avatarURL = Endpoint.resource(remoteAvatar).url - return AccountManager.instance.currentMailboxManager?.apiFetcher.authenticatedImageRequest(avatarURL) + if let localContact = local, localContact.imageDataAvailable { + var imageRequest = ImageRequest(id: localContact.identifier) { + guard let imageData = await localContact.pngImageData() else { + throw MailError.unknownError + } + + return imageData + } + imageRequest.options = [.disableDiskCache] + return imageRequest + } + + if let remoteAvatar = remote?.avatar { + let avatarURL = Endpoint.resource(remoteAvatar).url + return AccountManager.instance.currentMailboxManager?.apiFetcher.authenticatedImageRequest(avatarURL) + } + + return nil } public var initials: String { diff --git a/MailCore/Models/Recipient.swift b/MailCore/Models/Recipient.swift index 76510342b..609b3c83d 100644 --- a/MailCore/Models/Recipient.swift +++ b/MailCore/Models/Recipient.swift @@ -111,10 +111,6 @@ public class Recipient: EmbeddedObject, Codable { } extension Recipient: AvatarDisplayable { - public var localImage: Image? { - contact?.localImage - } - public var avatarImageRequest: ImageRequest? { guard !isCurrentUser else { return AccountManager.instance.currentAccount.user.avatarImageRequest diff --git a/MailCore/Utils/AvatarDisplayable.swift b/MailCore/Utils/AvatarDisplayable.swift index 06779f857..a3fb2c816 100644 --- a/MailCore/Utils/AvatarDisplayable.swift +++ b/MailCore/Utils/AvatarDisplayable.swift @@ -23,17 +23,12 @@ import SwiftUI import UIKit public protocol AvatarDisplayable { - var localImage: Image? { get } var avatarImageRequest: ImageRequest? { get } var initials: String { get } var initialsBackgroundColor: UIColor { get } } extension UserProfile: AvatarDisplayable { - public var localImage: Image? { - nil - } - public var avatarImageRequest: ImageRequest? { guard let avatarURL = URL(string: avatar) else { return nil } return ImageRequest(url: avatarURL) diff --git a/MailCore/Utils/LocalContactsHelper.swift b/MailCore/Utils/LocalContactsHelper.swift index 230c4325d..b73180a59 100644 --- a/MailCore/Utils/LocalContactsHelper.swift +++ b/MailCore/Utils/LocalContactsHelper.swift @@ -37,7 +37,14 @@ class LocalContactsHelper { static let shared = LocalContactsHelper() let store = CNContactStore() - let keysToFetch = ([CNContactIdentifierKey, CNContactEmailAddressesKey, CNContactImageDataKey, CNContactNicknameKey, CNContactOrganizationNameKey] as [CNKeyDescriptor]) + [CNContactFormatter.descriptorForRequiredKeys(for: .fullName)] + let keysToFetch = ([ + CNContactIdentifierKey, + CNContactEmailAddressesKey, + CNContactImageDataAvailableKey, + CNContactImageDataKey, + CNContactNicknameKey, + CNContactOrganizationNameKey + ] as [CNKeyDescriptor]) + [CNContactFormatter.descriptorForRequiredKeys(for: .fullName)] func enumerateContacts(usingBlock: @escaping (CNContact, UnsafeMutablePointer) -> Void) async { do { From 8a45079c00430e023cb80342483bc27f1a857b7c Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 28 Mar 2023 15:32:52 +0200 Subject: [PATCH 5/6] refactor: Remove useless Nuke+Authorization --- MailCore/Utils/Nuke+Authorization.swift | 33 ------------------------- 1 file changed, 33 deletions(-) delete mode 100644 MailCore/Utils/Nuke+Authorization.swift diff --git a/MailCore/Utils/Nuke+Authorization.swift b/MailCore/Utils/Nuke+Authorization.swift deleted file mode 100644 index 14239c1d3..000000000 --- a/MailCore/Utils/Nuke+Authorization.swift +++ /dev/null @@ -1,33 +0,0 @@ -/* - 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 Foundation -import Nuke -import UIKit - -public extension ImagePipeline { - func imageWithAuthentication(for url: URL) async throws -> UIImage { - var urlRequest = URLRequest(url: url) - urlRequest.addValue( - "Bearer \(AccountManager.instance.currentAccount.token.accessToken)", - forHTTPHeaderField: "Authorization" - ) - - return try await image(for: ImageRequest(urlRequest: urlRequest)) - } -} From a283681dd346b256657d1bb6a0a470ea9e9af15a Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Wed, 29 Mar 2023 17:51:38 +0200 Subject: [PATCH 6/6] fix(Recipient): Check is me Co-authored-by: Valentin Perignon --- MailCore/Models/Recipient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MailCore/Models/Recipient.swift b/MailCore/Models/Recipient.swift index db4a80589..e43fa3d85 100644 --- a/MailCore/Models/Recipient.swift +++ b/MailCore/Models/Recipient.swift @@ -112,7 +112,7 @@ public class Recipient: EmbeddedObject, Codable { extension Recipient: AvatarDisplayable { public var avatarImageRequest: ImageRequest? { - guard !isCurrentUser else { + guard !(isCurrentUser && isMe) else { return AccountManager.instance.currentAccount.user.avatarImageRequest } return contact?.avatarImageRequest