Skip to content

Commit

Permalink
Merge branch 'master' into contextMenu-on-thread
Browse files Browse the repository at this point in the history
Signed-off-by: Jordan <[email protected]>
  • Loading branch information
lebojo committed Sep 18, 2024
2 parents d0a4499 + 027c3ae commit ab67d39
Show file tree
Hide file tree
Showing 49 changed files with 557 additions and 518 deletions.
1 change: 1 addition & 0 deletions Mail/Components/ThreadCell/ThreadCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ struct ThreadCellDataHolder {
let isInWrittenByMeFolder: Bool

init(thread: Thread) {
// swiftlint:disable:next last_where
let lastMessageNotFromSent = thread.messages.filter(Self.lastMessageNotFromSentPredicate).last ?? thread.messages.last

date = thread.date.formatted(.thread(.list))
Expand Down
13 changes: 7 additions & 6 deletions Mail/Views/AI Writer/Proposition/AIDismissibleErrorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@ struct AIDismissibleErrorView: View {
InformationBlockView(
icon: MailResourcesAsset.warningFill.swiftUIImage,
message: error?.localizedDescription ?? "",
iconColor: MailResourcesAsset.orangeColor.swiftUIColor
) {
matomo.track(eventWithCategory: .aiWriter, name: "dismissError")
iconColor: MailResourcesAsset.orangeColor.swiftUIColor,
dismissHandler: { // swiftlint:disable:this trailing_closure
matomo.track(eventWithCategory: .aiWriter, name: "dismissError")

withAnimation {
isShowingError = false
withAnimation {
isShowingError = false
}
}
}
)
}
}
.onChange(of: error) { newError in
Expand Down
4 changes: 2 additions & 2 deletions Mail/Views/Bottom sheets/Actions/HeaderCloseButtonView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct HeaderCloseButtonView: View {
}
}

#Preview {
@available(iOS 17.0, *)
#Preview(traits: .sizeThatFitsLayout) {
HeaderCloseButtonView(title: "View") {}
.previewLayout(.sizeThatFits)
}
1 change: 1 addition & 0 deletions Mail/Views/Menu Drawer/Actions/HelpView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ struct HelpView: View {
.background(MailResourcesAsset.backgroundColor.swiftUIColor)
.navigationBarTitle(MailResourcesStrings.Localizable.buttonHelp, displayMode: .inline)
.customAlert(item: $updateVersionAlert) { action in
// swiftlint:disable:next trailing_closure
UpdateVersionAlertView(onLaterPressed: {
openURL(action.destination)
})
Expand Down
2 changes: 1 addition & 1 deletion Mail/Views/Menu Drawer/Folders/FolderCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import SwiftUI
import SwiftUIMacros

extension EnvironmentValues {
@EnvironmentValue
@EnvironmentKey
var folderCellType = FolderCell.CellType.menuDrawer
}

Expand Down
2 changes: 1 addition & 1 deletion Mail/Views/Menu Drawer/MailboxManagement/MailboxCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import SwiftUI
import SwiftUIMacros

extension EnvironmentValues {
@EnvironmentValue
@EnvironmentKey
var mailboxCellStyle = MailboxCell.Style.menuDrawer
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ struct SettingsAccountManagementView: View {
}
}

extension ApiToken: Identifiable {
extension ApiToken: @retroactive Identifiable {
public var id: String {
return "\(userId)\(accessToken)"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ struct SettingsNotificationsView: View {
}
}
}
// swiftlint:disable:next trailing_closure
.sceneLifecycle(willEnterForeground: {
settingsNotificationEnabled { enabled in
showWarning = !enabled
Expand Down
1 change: 1 addition & 0 deletions Mail/Views/SplitView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ struct SplitView: View {
SafariWebView(url: safariContent.url)
.ignoresSafeArea()
}
// swiftlint:disable:next trailing_closure
.sceneLifecycle(willEnterForeground: {
Task {
// We need to write in Task instead of async let to avoid being cancelled to early
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import MailCoreUI
import MailResources
import SwiftUI

extension MailResourcesImages: Identifiable {
extension MailResourcesImages: @retroactive Identifiable {
public var id: String {
name
}
Expand Down
2 changes: 2 additions & 0 deletions Mail/Views/Thread List/ThreadListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ struct ThreadListView: View {
.onChange(of: multipleSelectionViewModel.isEnabled) { isEnabled in
scrollObserver.shouldObserve = !isEnabled
}
// swiftlint:disable:next trailing_closure
.sceneLifecycle(willEnterForeground: {
updateFetchingTask()
})
Expand All @@ -210,6 +211,7 @@ struct ThreadListView: View {
FlushFolderAlertView(flushAlert: item, folder: viewModel.frozenFolder)
}
.customAlert(isPresented: $isShowingUpdateAlert) {
// swiftlint:disable:next trailing_closure
UpdateVersionAlertView(onDismiss: {
hasDismissedUpdateVersionView = true
})
Expand Down
130 changes: 130 additions & 0 deletions Mail/Views/Thread/Message/BodyImageProcessor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
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 Foundation
import MailCore
import OSLog
import UIKit

struct BodyImageProcessor {
private let bodyImageMutator = BodyImageMutator()

/// Download and encode all images for the current chunk in parallel.
public func fetchBase64Images(
_ attachments: ArraySlice<Attachment>,
mailboxManager: MailboxManager
) async -> [ImageBase64AndMime?] {
// Force a fixed max concurrency to be a nice citizen to the network.
let base64Images: [ImageBase64AndMime?] = await attachments
.concurrentMap(customConcurrency: Constants.concurrentNetworkCalls) { attachment in
do {
let attachmentData = try await mailboxManager.attachmentData(attachment)

// Skip compression on non static images types or already heic sources
guard attachment.mimeType.contains("jpg")
|| attachment.mimeType.contains("jpeg")
|| attachment.mimeType.contains("png") else {
let base64String = attachmentData.base64EncodedString()
return ImageBase64AndMime(base64String, attachment.mimeType)
}

// Skip compression with lockdown mode enables as images can glitch
let isLockdownModeEnabled = (UserDefaults.standard.object(forKey: "LDMGlobalEnabled") as? Bool) ?? false
guard !isLockdownModeEnabled else {
let base64String = attachmentData.base64EncodedString()
return ImageBase64AndMime(base64String, attachment.mimeType)
}

let compressedImage = compressedBase64ImageAndMime(
attachmentData: attachmentData,
attachmentMime: attachment.mimeType
)
return compressedImage
} catch {
Logger.general.error("Error \(error) : Failed to fetch data for attachment: \(attachment)")
return nil
}
}

assert(base64Images.count == attachments.count, "Arrays count should match")
return base64Images
}

/// Try to compress the attachment with the best matched algorithm. Trade CPU cycles to reduce render time and memory usage.
private func compressedBase64ImageAndMime(attachmentData: Data, attachmentMime: String) -> ImageBase64AndMime {
guard #available(iOS 17.0, *) else {
let base64String = attachmentData.base64EncodedString()
return ImageBase64AndMime(base64String, attachmentMime)
}

// On iOS17 Safari and iOS has support for heic. Quality is unchanged. Size is halved.
let image = UIImage(data: attachmentData)
guard let imageCompressed = image?.heicData(), imageCompressed.count < attachmentData.count else {
let base64String = attachmentData.base64EncodedString()
return ImageBase64AndMime(base64String, attachmentMime)
}

let base64String = imageCompressed.base64EncodedString()
return ImageBase64AndMime(base64String, "image/heic")
}

/// Inject base64 images in a body
public func injectImagesInBody(
body: String?,
attachments: ArraySlice<Attachment>,
base64Images: [ImageBase64AndMime?]
) async -> String? {
guard let body, !body.isEmpty else {
return nil
}

var workingBody = body
for (index, attachment) in attachments.enumerated() {
guard !Task.isCancelled else {
break
}

guard let contentId = attachment.contentId,
let base64Image = base64Images[safe: index] as? ImageBase64AndMime else {
continue
}

bodyImageMutator.replaceContentIdForBase64Image(
in: &workingBody,
contentId: contentId,
mimeType: base64Image.mimeType,
contentBase64Encoded: base64Image.imageEncoded
)
}
return workingBody
}
}

struct BodyImageMutator {
func replaceContentIdForBase64Image(
in body: inout String,
contentId: String,
mimeType: String,
contentBase64Encoded: String
) {
body = body.replacingOccurrences(
of: "cid:\(contentId)",
with: "data:\(mimeType);base64,\(contentBase64Encoded)"
)
}
}
Loading

0 comments on commit ab67d39

Please sign in to comment.