From c6b09c6e1a2afcb2b04d69606f78c0e110ffec4a Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Wed, 26 Apr 2023 09:38:34 +0200 Subject: [PATCH 01/30] feat: Use presentationDetents for panels on iOS 16 --- Mail/Utils/FloatingPanelHelper.swift | 99 +++++++++++++++++-- .../Bottom sheets/Actions/ActionsView.swift | 8 +- .../Actions/ActionsViewModel.swift | 23 ++--- .../Actions/ReplyActionsView.swift | 2 - .../Actions/ReportJunkView.swift | 7 +- Mail/Views/Search/SearchView.swift | 4 +- Mail/Views/SplitView.swift | 23 +---- Mail/Views/Thread List/ThreadListView.swift | 24 +++-- .../Thread List/ThreadListViewModel.swift | 4 +- Mail/Views/Thread/ThreadView.swift | 8 +- MailCore/UI/UIConstants.swift | 5 + 11 files changed, 128 insertions(+), 79 deletions(-) diff --git a/Mail/Utils/FloatingPanelHelper.swift b/Mail/Utils/FloatingPanelHelper.swift index f67705577..21083356e 100644 --- a/Mail/Utils/FloatingPanelHelper.swift +++ b/Mail/Utils/FloatingPanelHelper.swift @@ -30,6 +30,25 @@ class AdaptiveDriveFloatingPanelController: FloatingPanelController { contentSizeObservation?.invalidate() } + init() { + super.init(delegate: nil) + let appearance = SurfaceAppearance() + appearance.cornerRadius = 20 + appearance.backgroundColor = MailResourcesAsset.backgroundSecondaryColor.color + surfaceView.appearance = appearance + surfaceView.grabberHandlePadding = 16 + surfaceView.grabberHandleSize = CGSize(width: 45, height: 5) + surfaceView.grabberHandle.barColor = MailResourcesAsset.elementsColor.color + surfaceView.contentPadding = UIEdgeInsets(top: 32, left: 0, bottom: 16, right: 0) + backdropView.dismissalTapGestureRecognizer.isEnabled = true + isRemovalInteractionEnabled = true + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) updateMargins() @@ -107,17 +126,7 @@ class DisplayedFloatingPanelState: ObservableObject, FloatingPanelControl init() { floatingPanel = AdaptiveDriveFloatingPanelController() - let appearance = SurfaceAppearance() - appearance.cornerRadius = 20 - appearance.backgroundColor = MailResourcesAsset.backgroundSecondaryColor.color floatingPanel.delegate = self - floatingPanel.surfaceView.appearance = appearance - floatingPanel.surfaceView.grabberHandlePadding = 16 - floatingPanel.surfaceView.grabberHandleSize = CGSize(width: 45, height: 5) - floatingPanel.surfaceView.grabberHandle.barColor = MailResourcesAsset.elementsColor.color - floatingPanel.surfaceView.contentPadding = UIEdgeInsets(top: 32, left: 0, bottom: 16, right: 0) - floatingPanel.backdropView.dismissalTapGestureRecognizer.isEnabled = true - floatingPanel.isRemovalInteractionEnabled = true } func createPanelContent(content: Content, halfOpening: Bool) { @@ -173,3 +182,73 @@ extension View { return self } } + +extension View { + func floatingPanel(isPresented: Binding, + @ViewBuilder content: @escaping () -> Content) -> some View { + sheet(isPresented: isPresented) { + if #available(iOS 16.0, *) { + content().modifier(SelfSizingPanelViewModifier()) + } else { + content().modifier(SelfSizingPanelBackportViewModifier()) + } + } + } + + func floatingPanel(item: Binding, + @ViewBuilder content: @escaping (Item) -> Content) -> some View { + sheet(item: item) { item in + if #available(iOS 16.0, *) { + content(item).modifier(SelfSizingPanelViewModifier()) + } else { + content(item).modifier(SelfSizingPanelBackportViewModifier()) + } + } + } + + func ikPresentationCornerRadius(_ cornerRadius: CGFloat?) -> some View { + if #available(iOS 16.4, *) { + return presentationCornerRadius(cornerRadius) + } else { + return introspectViewController { viewController in + viewController.sheetPresentationController?.preferredCornerRadius = cornerRadius + } + } + } +} + +struct SelfSizingPanelBackportViewModifier: ViewModifier { + func body(content: Content) -> some View {} +} + +@available(iOS 16.0, *) +struct SelfSizingPanelViewModifier: ViewModifier { + @State var currentDetents: Set = [.height(0)] + @State var selection: PresentationDetent = .height(0) + + func body(content: Content) -> some View { + ScrollView { + content + .padding(.bottom, 16) + } + .padding(.top, 24) + .introspectScrollView { scrollView in + guard selection != .height(scrollView.contentSize.height) else { return } + + scrollView.isScrollEnabled = scrollView.contentSize.height > (scrollView.window?.bounds.height ?? 0) + DispatchQueue.main.async { + currentDetents = [.height(0), .height(scrollView.contentSize.height)] + selection = .height(scrollView.contentSize.height) + + // Hack to let time for the animation to finish, after animation is complete we can modify the state again + // if we don't do this the animation is cut before finishing + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + currentDetents = [.height(scrollView.contentSize.height)] + } + } + } + .presentationDetents(currentDetents, selection: $selection) + .presentationDragIndicator(.visible) + .ikPresentationCornerRadius(20) + } +} diff --git a/Mail/Views/Bottom sheets/Actions/ActionsView.swift b/Mail/Views/Bottom sheets/Actions/ActionsView.swift index 80ccb48cd..532860b80 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsView.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsView.swift @@ -27,9 +27,6 @@ struct ActionsView: View { init(mailboxManager: MailboxManager, target: ActionsTarget, - state: ThreadBottomSheet, - globalSheet: GlobalBottomSheet, - globalAlert: GlobalAlert? = nil, moveSheet: MoveSheet? = nil, replyHandler: ((Message, ReplyMode) -> Void)? = nil, completionHandler: (() -> Void)? = nil) { @@ -40,9 +37,6 @@ struct ActionsView: View { viewModel = ActionsViewModel(mailboxManager: mailboxManager, target: target, - state: state, - globalSheet: globalSheet, - globalAlert: globalAlert, moveSheet: moveSheet, matomoCategory: matomoCategory, replyHandler: replyHandler, @@ -74,6 +68,7 @@ struct ActionsView: View { } } +/* struct ActionsView_Previews: PreviewProvider { static var previews: some View { ActionsView(mailboxManager: PreviewHelper.sampleMailboxManager, @@ -84,6 +79,7 @@ struct ActionsView_Previews: PreviewProvider { .accentColor(AccentColor.pink.primary.swiftUIColor) } } +*/ struct QuickActionView: View { @ObservedObject var viewModel: ActionsViewModel diff --git a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift index 49eed3801..6dfdf505c 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift @@ -173,7 +173,11 @@ struct Action: Identifiable, Equatable { } } -enum ActionsTarget: Equatable { +enum ActionsTarget: Equatable, Identifiable { + var id: String { + return UUID().uuidString + } + case threads([Thread], Bool) case message(Message) @@ -199,9 +203,6 @@ enum ActionsTarget: Equatable { @MainActor class ActionsViewModel: ObservableObject { private let mailboxManager: MailboxManager private let target: ActionsTarget - private let state: ThreadBottomSheet - private let globalSheet: GlobalBottomSheet - private let globalAlert: GlobalAlert? private let moveSheet: MoveSheet? private let replyHandler: ((Message, ReplyMode) -> Void)? private let completionHandler: (() -> Void)? @@ -215,18 +216,12 @@ enum ActionsTarget: Equatable { init(mailboxManager: MailboxManager, target: ActionsTarget, - state: ThreadBottomSheet, - globalSheet: GlobalBottomSheet, - globalAlert: GlobalAlert? = nil, moveSheet: MoveSheet? = nil, matomoCategory: MatomoUtils.EventCategory? = nil, replyHandler: ((Message, ReplyMode) -> Void)? = nil, completionHandler: (() -> Void)? = nil) { self.mailboxManager = mailboxManager self.target = target.freeze() - self.state = state - self.globalSheet = globalSheet - self.globalAlert = globalAlert self.moveSheet = moveSheet self.replyHandler = replyHandler self.completionHandler = completionHandler @@ -289,8 +284,6 @@ enum ActionsTarget: Equatable { } func didTap(action: Action) async throws { - state.close() - globalSheet.close() if let matomoCategory, let matomoName = action.matomoName { if case let .threads(threads, isMultipleSelectionEnabled) = target, isMultipleSelectionEnabled { matomo.trackBulkEvent(eventWithCategory: matomoCategory, name: matomoName.capitalized, numberOfItems: threads.count) @@ -439,7 +432,7 @@ enum ActionsTarget: Equatable { } private func displayReportJunk() { - globalSheet.open(state: .reportJunk(threadBottomSheet: state, target: target)) + //globalSheet.open(state: .reportJunk(threadBottomSheet: state, target: target)) } private func block() async throws { @@ -454,7 +447,7 @@ enum ActionsTarget: Equatable { private func phishing() async throws { // This action is only available on a single message guard case let .message(message) = target else { return } - globalAlert?.state = .reportPhishing(message: message) + // globalAlert?.state = .reportPhishing(message: message) } private func printAction() { @@ -465,7 +458,7 @@ enum ActionsTarget: Equatable { private func report() { // This action is only available on a single message guard case let .message(message) = target else { return } - globalAlert?.state = .reportDisplayProblem(message: message) + // globalAlert?.state = .reportDisplayProblem(message: message) } private func editMenu() { diff --git a/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift b/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift index 7fbf03a45..6e3b4ae79 100644 --- a/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift +++ b/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift @@ -33,8 +33,6 @@ struct ReplyActionsView: View { replyHandler: @escaping (Message, ReplyMode) -> Void) { viewModel = ActionsViewModel(mailboxManager: mailboxManager, target: target, - state: state, - globalSheet: globalSheet, matomoCategory: .replyBottomSheet, replyHandler: replyHandler) } diff --git a/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift b/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift index b148c0f89..ef47fd4ea 100644 --- a/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift +++ b/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift @@ -32,11 +32,8 @@ struct ReportJunkView: View { globalSheet: GlobalBottomSheet, globalAlert: GlobalAlert) { viewModel = ActionsViewModel(mailboxManager: mailboxManager, - target: target, - state: state, - globalSheet: globalSheet, - globalAlert: globalAlert) - if case let .message(message) = target { + target: target) + if case .message(let message) = target { let spam = message.folder?.role == .spam actions.append(contentsOf: [ spam ? .nonSpam : .spam, diff --git a/Mail/Views/Search/SearchView.swift b/Mail/Views/Search/SearchView.swift index ef3430a60..a4763a896 100644 --- a/Mail/Views/Search/SearchView.swift +++ b/Mail/Views/Search/SearchView.swift @@ -97,9 +97,7 @@ struct SearchView: View { .floatingPanel(state: bottomSheet, halfOpening: true) { if case .actions(let target) = bottomSheet.state, !target.isInvalidated { ActionsView(mailboxManager: viewModel.mailboxManager, - target: target, - state: bottomSheet, - globalSheet: globalBottomSheet) { message, replyMode in + target: target) { message, replyMode in messageReply = MessageReply(message: message, replyMode: replyMode) } } diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index 70fa00756..f2f7277cf 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -54,7 +54,7 @@ struct SplitView: View { @State var splitViewController: UISplitViewController? @StateObject private var navigationDrawerController = NavigationDrawerState() - @Environment(\.horizontalSizeClass) var sizeClass + @Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.verticalSizeClass) var verticalSizeClass @Environment(\.window) var window @@ -64,7 +64,7 @@ struct SplitView: View { @StateObject private var splitViewManager: SplitViewManager var isCompact: Bool { - sizeClass == .compact || verticalSizeClass == .compact + UIConstants.isCompact(horizontalSizeClass: horizontalSizeClass, verticalSizeClass: verticalSizeClass) } init(mailboxManager: MailboxManager) { @@ -129,24 +129,7 @@ struct SplitView: View { setupBehaviour(orientation: interfaceOrientation) splitViewController.preferredDisplayMode = .twoDisplaceSecondary } - .floatingPanel(state: bottomSheet) { - switch bottomSheet.state { - case .getMoreStorage: - MoreStorageView() - case .restoreEmails: - RestoreEmailsView(mailboxManager: mailboxManager) - case .reportJunk(let threadBottomSheet, let target): - ReportJunkView( - mailboxManager: mailboxManager, - target: target, - state: threadBottomSheet, - globalSheet: bottomSheet, - globalAlert: alert - ) - case .none: - EmptyView() - } - } + .customAlert(isPresented: $alert.isShowing) { switch alert.state { case .reportPhishing(let message): diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index 2d0d07376..f123a0940 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -212,19 +212,16 @@ struct ThreadListView: View { matomo.track(eventWithCategory: .newMessage, name: "openFromFab") isShowingComposeNewMessageView.toggle() } - .floatingPanel(state: bottomSheet, halfOpening: true) { - if case .actions(let target) = bottomSheet.state, !target.isInvalidated { - ActionsView(mailboxManager: viewModel.mailboxManager, - target: target, - state: bottomSheet, - globalSheet: globalBottomSheet, moveSheet: moveSheet) { message, replyMode in - messageReply = MessageReply(message: message, replyMode: replyMode) - } completionHandler: { - bottomSheet.close() - multipleSelectionViewModel.isEnabled = false - } + .floatingPanel(item: $viewModel.actionsTarget, content: { target in + ActionsView(mailboxManager: viewModel.mailboxManager, + target: target, + moveSheet: moveSheet) { message, replyMode in + viewModel.actionsTarget = nil + messageReply = MessageReply(message: message, replyMode: replyMode) + } completionHandler: { + multipleSelectionViewModel.isEnabled = false } - } + }) .onAppear { networkMonitor.start() viewModel.globalBottomSheet = globalBottomSheet @@ -367,7 +364,8 @@ private struct ThreadListToolbar: ViewModifier { ForEach(multipleSelectionViewModel.toolbarActions) { action in ToolbarButton( text: action.shortTitle ?? action.title, - icon: action.icon) { + icon: action.icon + ) { Task { await tryOrDisplayError { try await multipleSelectionViewModel.didTap( diff --git a/Mail/Views/Thread List/ThreadListViewModel.swift b/Mail/Views/Thread List/ThreadListViewModel.swift index 54961c349..aeeed7908 100644 --- a/Mail/Views/Thread List/ThreadListViewModel.swift +++ b/Mail/Views/Thread List/ThreadListViewModel.swift @@ -105,6 +105,8 @@ class DateSection: Identifiable { @Published var isLoadingPage = false @Published var lastUpdate: Date? + @Published var actionsTarget: ActionsTarget? + // Used to know thread location private var selectedThreadIndex: Int? @@ -310,7 +312,7 @@ class DateSection: Identifiable { case .spam: try await toggleSpam(thread: thread) case .quickAction: - bottomSheet.open(state: .actions(.threads([thread.thaw() ?? thread], false))) + actionsTarget = .threads([thread.thaw() ?? thread], false) case .none: break case .moveToInbox: diff --git a/Mail/Views/Thread/ThreadView.swift b/Mail/Views/Thread/ThreadView.swift index d8427c718..a09b53100 100644 --- a/Mail/Views/Thread/ThreadView.swift +++ b/Mail/Views/Thread/ThreadView.swift @@ -140,7 +140,7 @@ struct ThreadView: View { MoveEmailView.sheetView(mailboxManager: mailboxManager, from: folderId, moveHandler: handler) } } - .floatingPanel(state: bottomSheet) { + /*.floatingPanel(state: bottomSheet) { switch bottomSheet.state { case .contact(let recipient, let isRemote): ContactActionsView( @@ -162,8 +162,8 @@ struct ThreadView: View { case .none: EmptyView() } - } - .floatingPanel(state: threadBottomSheet, halfOpening: true) { + }*/ + /*.floatingPanel(state: threadBottomSheet, halfOpening: true) { if case .actions(let target) = threadBottomSheet.state, !target.isInvalidated { ActionsView(mailboxManager: mailboxManager, target: target, @@ -174,7 +174,7 @@ struct ThreadView: View { messageReply = MessageReply(message: message, replyMode: replyMode) } } - } + }*/ .onChange(of: thread.messages) { newMessagesList in if newMessagesList.isEmpty { showEmptyView = true diff --git a/MailCore/UI/UIConstants.swift b/MailCore/UI/UIConstants.swift index 1c2b32a15..2a4a9ae5f 100644 --- a/MailCore/UI/UIConstants.swift +++ b/MailCore/UI/UIConstants.swift @@ -18,6 +18,7 @@ import Foundation import MailResources +import SwiftUI import UIKit public enum BarAppearanceConstants { @@ -96,4 +97,8 @@ public enum UIConstants { public static let bottomSheetHorizontalPadding: CGFloat = 24 public static let componentsMaxWidth: CGFloat = 496 + + public static func isCompact(horizontalSizeClass: UserInterfaceSizeClass?, verticalSizeClass: UserInterfaceSizeClass?) -> Bool { + return horizontalSizeClass == .compact || verticalSizeClass == .compact + } } From 3d30413e704e62e04a89c7dabf58e44fda9c2c27 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Wed, 26 Apr 2023 11:27:25 +0200 Subject: [PATCH 02/30] feat: Backport panel to iOS 15 --- Mail/Utils/FloatingPanelHelper.swift | 39 +++++++++++++++++++++++----- Project.swift | 6 +++-- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/Mail/Utils/FloatingPanelHelper.swift b/Mail/Utils/FloatingPanelHelper.swift index 21083356e..10150d8a2 100644 --- a/Mail/Utils/FloatingPanelHelper.swift +++ b/Mail/Utils/FloatingPanelHelper.swift @@ -20,6 +20,7 @@ import Combine import FloatingPanel import MailResources import SwiftUI +import SwiftUIBackports class AdaptiveDriveFloatingPanelController: FloatingPanelController { private var contentSizeObservation: NSKeyValueObservation? @@ -217,33 +218,57 @@ extension View { } } +@available(iOS, introduced: 15, deprecated: 16, message: "Use native way") struct SelfSizingPanelBackportViewModifier: ViewModifier { - func body(content: Content) -> some View {} + @State var currentDetents: Set = [.medium] + private let topPadding: CGFloat = 24 + + func body(content: Content) -> some View { + ScrollView { + content + .padding(.bottom, 16) + } + .padding(.top, topPadding) + .introspectScrollView { scrollView in + guard !currentDetents.contains(.large) else { return } + let totalPanelContentHeight = scrollView.contentSize.height + topPadding + + scrollView.isScrollEnabled = totalPanelContentHeight > (scrollView.window?.bounds.height ?? 0) + if totalPanelContentHeight > (scrollView.window?.bounds.height ?? 0) / 2 { + currentDetents = [.medium, .large] + } + } + .backport.presentationDragIndicator(.visible) + .backport.presentationDetents(currentDetents) + .ikPresentationCornerRadius(20) + } } @available(iOS 16.0, *) struct SelfSizingPanelViewModifier: ViewModifier { @State var currentDetents: Set = [.height(0)] @State var selection: PresentationDetent = .height(0) + private let topPadding: CGFloat = 24 func body(content: Content) -> some View { ScrollView { content .padding(.bottom, 16) } - .padding(.top, 24) + .padding(.top, topPadding) .introspectScrollView { scrollView in - guard selection != .height(scrollView.contentSize.height) else { return } + let totalPanelContentHeight = scrollView.contentSize.height + topPadding + guard selection != .height(totalPanelContentHeight) else { return } - scrollView.isScrollEnabled = scrollView.contentSize.height > (scrollView.window?.bounds.height ?? 0) + scrollView.isScrollEnabled = totalPanelContentHeight > (scrollView.window?.bounds.height ?? 0) DispatchQueue.main.async { - currentDetents = [.height(0), .height(scrollView.contentSize.height)] - selection = .height(scrollView.contentSize.height) + currentDetents = [.height(0), .height(totalPanelContentHeight)] + selection = .height(totalPanelContentHeight) // Hack to let time for the animation to finish, after animation is complete we can modify the state again // if we don't do this the animation is cut before finishing DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - currentDetents = [.height(scrollView.contentSize.height)] + currentDetents = [selection] } } } diff --git a/Project.swift b/Project.swift index b14aae396..7bdabeadc 100644 --- a/Project.swift +++ b/Project.swift @@ -47,7 +47,8 @@ let project = Project(name: "Mail", .package(url: "https://github.com/SCENEE/FloatingPanel", .upToNextMajor(from: "2.0.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")) + .package(url: "https://github.com/scinfu/SwiftSoup", .upToNextMajor(from: "2.5.3")), + .package(url: "https://github.com/shaps80/SwiftUIBackports", .upToNextMajor(from: "1.15.1")) ], targets: [ Target(name: "Mail", @@ -77,7 +78,8 @@ let project = Project(name: "Mail", .package(product: "Shimmer"), .package(product: "WrappingHStack"), .package(product: "FloatingPanel"), - .package(product: "Lottie") + .package(product: "Lottie"), + .package(product: "SwiftUIBackports") ], settings: .settings(base: baseSettings), environment: ["hostname": "\(ProcessInfo.processInfo.hostName)."]), From 240f06d38b1ba7f00cea2d88d83a534a732b313d Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Wed, 26 Apr 2023 11:54:43 +0200 Subject: [PATCH 03/30] feat: ContactActionsView native panel --- .../Bottom sheets/ContactActionsView.swift | 38 ++++++++++--------- Mail/Views/Thread/MessageHeaderView.swift | 20 +++++----- Mail/Views/Thread/ThreadView.swift | 1 - 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/Mail/Views/Bottom sheets/ContactActionsView.swift b/Mail/Views/Bottom sheets/ContactActionsView.swift index af0cfc354..837b537e4 100644 --- a/Mail/Views/Bottom sheets/ContactActionsView.swift +++ b/Mail/Views/Bottom sheets/ContactActionsView.swift @@ -24,13 +24,24 @@ import MailResources import SwiftUI struct ContactActionsView: View { - var recipient: Recipient - var isRemoteContact: Bool - @ObservedObject var bottomSheet: MessageBottomSheet - var mailboxManager: MailboxManager + @EnvironmentObject var mailboxManager: MailboxManager + @Environment(\.dismiss) var dismiss + @LazyInjectService private var matomo: MatomoUtils + @State private var writtenToRecipient: Recipient? - @LazyInjectService private var matomo: MatomoUtils + private let recipient: Recipient + private let actions: [ContactAction] + + init(recipient: Recipient) { + self.recipient = recipient + let isRemoteContact = AccountManager.instance.currentContactManager?.getContact(for: recipient)?.remote != nil + if isRemoteContact { + actions = [.writeEmailAction, .copyEmailAction] + } else { + actions = [.writeEmailAction, .addContactsAction, .copyEmailAction] + } + } private struct ContactAction: Hashable { let name: String @@ -54,13 +65,6 @@ struct ContactActionsView: View { ) } - private var actions: [ContactAction] { - if isRemoteContact { - return [.writeEmailAction, .copyEmailAction] - } - return [.writeEmailAction, .addContactsAction, .copyEmailAction] - } - var body: some View { VStack(alignment: .leading, spacing: 16) { HStack { @@ -107,10 +111,10 @@ struct ContactActionsView: View { case .writeEmailAction: writeEmail() case .addContactsAction: - bottomSheet.close() + dismiss() addToContacts() case .copyEmailAction: - bottomSheet.close() + dismiss() copyEmail() default: return @@ -138,9 +142,7 @@ struct ContactActionsView: View { struct ContactActionsView_Previews: PreviewProvider { static var previews: some View { - ContactActionsView(recipient: PreviewHelper.sampleRecipient1, - isRemoteContact: false, - bottomSheet: MessageBottomSheet(), - mailboxManager: PreviewHelper.sampleMailboxManager) + ContactActionsView(recipient: PreviewHelper.sampleRecipient1) + .environmentObject(PreviewHelper.sampleMailboxManager) } } diff --git a/Mail/Views/Thread/MessageHeaderView.swift b/Mail/Views/Thread/MessageHeaderView.swift index 916909028..1919a7aa7 100644 --- a/Mail/Views/Thread/MessageHeaderView.swift +++ b/Mail/Views/Thread/MessageHeaderView.swift @@ -26,7 +26,9 @@ import SwiftUI struct MessageHeaderView: View { @State private var editedDraft: Draft? - @State var messageReply: MessageReply? + @State private var messageReply: MessageReply? + @State private var contactViewRecipient: Recipient? + @ObservedRealmObject var message: Message @Binding var isHeaderExpanded: Bool @Binding var isMessageExpanded: Bool @@ -52,11 +54,13 @@ struct MessageHeaderView: View { } moreButtonTapped: { threadBottomSheet.open(state: .actions(.message(message.thaw() ?? message))) } recipientTapped: { recipient in - openContact(recipient: recipient) + contactViewRecipient = recipient } if isHeaderExpanded { - MessageHeaderDetailView(message: message, recipientTapped: openContact(recipient:)) + MessageHeaderDetailView(message: message) { recipient in + contactViewRecipient = recipient + } } } .contentShape(Rectangle()) @@ -77,13 +81,9 @@ struct MessageHeaderView: View { .sheet(item: $messageReply) { messageReply in ComposeMessageView.replyOrForwardMessage(messageReply: messageReply, mailboxManager: mailboxManager) } - } - - private func openContact(recipient: Recipient) { - let isRemoteContact = AccountManager.instance.currentContactManager?.getContact(for: recipient)?.remote != nil - bottomSheet.open( - state: .contact(recipient, isRemote: isRemoteContact) - ) + .floatingPanel(item: $contactViewRecipient) { recipient in + ContactActionsView(recipient: recipient) + } } private func deleteDraft() { diff --git a/Mail/Views/Thread/ThreadView.swift b/Mail/Views/Thread/ThreadView.swift index a09b53100..fcccce1fe 100644 --- a/Mail/Views/Thread/ThreadView.swift +++ b/Mail/Views/Thread/ThreadView.swift @@ -129,7 +129,6 @@ struct ThreadView: View { } } } - .environmentObject(mailboxManager) .environmentObject(bottomSheet) .environmentObject(threadBottomSheet) .sheet(item: $messageReply) { messageReply in From 303b2da8296f80dd364b4a408572cd5b241655f1 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Wed, 26 Apr 2023 13:21:29 +0200 Subject: [PATCH 04/30] feat: ReplyActionsView native panel --- .../Bottom sheets/Actions/ActionsView.swift | 2 ++ .../Actions/ReplyActionsView.swift | 6 +----- Mail/Views/Thread/MessageHeaderView.swift | 12 ++++++++++-- Mail/Views/Thread/ThreadView.swift | 18 +++++++++++------- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/Mail/Views/Bottom sheets/Actions/ActionsView.swift b/Mail/Views/Bottom sheets/Actions/ActionsView.swift index 532860b80..dbba98846 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsView.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsView.swift @@ -82,6 +82,7 @@ struct ActionsView_Previews: PreviewProvider { */ struct QuickActionView: View { + @Environment(\.dismiss) var dismiss @ObservedObject var viewModel: ActionsViewModel let action: Action @@ -89,6 +90,7 @@ struct QuickActionView: View { var body: some View { Button { + dismiss() Task { await tryOrDisplayError { try await viewModel.didTap(action: action) diff --git a/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift b/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift index 6e3b4ae79..ee2505794 100644 --- a/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift +++ b/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift @@ -28,8 +28,6 @@ struct ReplyActionsView: View { init(mailboxManager: MailboxManager, target: ActionsTarget, - state: ThreadBottomSheet, - globalSheet: GlobalBottomSheet, replyHandler: @escaping (Message, ReplyMode) -> Void) { viewModel = ActionsViewModel(mailboxManager: mailboxManager, target: target, @@ -54,9 +52,7 @@ struct ReplyActionsView: View { struct ReplyActionsView_Previews: PreviewProvider { static var previews: some View { ReplyActionsView(mailboxManager: PreviewHelper.sampleMailboxManager, - target: .threads([PreviewHelper.sampleThread], false), - state: ThreadBottomSheet(), - globalSheet: GlobalBottomSheet()) { _, _ in /* Preview */ } + target: .threads([PreviewHelper.sampleThread], false)) { _, _ in /* Preview */ } .accentColor(AccentColor.pink.primary.swiftUIColor) } } diff --git a/Mail/Views/Thread/MessageHeaderView.swift b/Mail/Views/Thread/MessageHeaderView.swift index 1919a7aa7..20feffa42 100644 --- a/Mail/Views/Thread/MessageHeaderView.swift +++ b/Mail/Views/Thread/MessageHeaderView.swift @@ -28,13 +28,13 @@ struct MessageHeaderView: View { @State private var editedDraft: Draft? @State private var messageReply: MessageReply? @State private var contactViewRecipient: Recipient? + @State private var replyOrReplyAllMessage: Message? @ObservedRealmObject var message: Message @Binding var isHeaderExpanded: Bool @Binding var isMessageExpanded: Bool @EnvironmentObject var mailboxManager: MailboxManager - @EnvironmentObject var bottomSheet: MessageBottomSheet @EnvironmentObject var threadBottomSheet: ThreadBottomSheet @LazyInjectService private var matomo: MatomoUtils @@ -47,7 +47,7 @@ struct MessageHeaderView: View { deleteDraftTapped: deleteDraft) { matomo.track(eventWithCategory: .messageActions, name: "reply") if message.canReplyAll { - bottomSheet.open(state: .replyOption(message, isThread: false)) + replyOrReplyAllMessage = message } else { messageReply = MessageReply(message: message, replyMode: .reply) } @@ -84,6 +84,14 @@ struct MessageHeaderView: View { .floatingPanel(item: $contactViewRecipient) { recipient in ContactActionsView(recipient: recipient) } + .floatingPanel(item: $replyOrReplyAllMessage) { message in + ReplyActionsView( + mailboxManager: mailboxManager, + target: .message(message) + ) { message, replyMode in + messageReply = MessageReply(message: message, replyMode: replyMode) + } + } } private func deleteDraft() { diff --git a/Mail/Views/Thread/ThreadView.swift b/Mail/Views/Thread/ThreadView.swift index fcccce1fe..c271c7e72 100644 --- a/Mail/Views/Thread/ThreadView.swift +++ b/Mail/Views/Thread/ThreadView.swift @@ -49,14 +49,12 @@ struct ThreadView: View { @State private var headerHeight: CGFloat = 0 @State private var displayNavigationTitle = false @State private var messageReply: MessageReply? + @State private var replyOrReplyAllThread: Thread? @StateObject private var moveSheet = MoveSheet() - @StateObject private var bottomSheet = MessageBottomSheet() - @StateObject private var threadBottomSheet = ThreadBottomSheet() @State private var showEmptyView = false - @EnvironmentObject var globalBottomSheet: GlobalBottomSheet @EnvironmentObject var globalAlert: GlobalAlert @Environment(\.verticalSizeClass) var sizeClass @Environment(\.dismiss) var dismiss @@ -125,12 +123,10 @@ struct ThreadView: View { } ToolbarButton(text: MailResourcesStrings.Localizable.buttonMore, icon: MailResourcesAsset.plusActions.swiftUIImage) { - threadBottomSheet.open(state: .actions(.threads([thread.thaw() ?? thread], false))) + //threadBottomSheet.open(state: .actions(.threads([thread.thaw() ?? thread], false))) } } } - .environmentObject(bottomSheet) - .environmentObject(threadBottomSheet) .sheet(item: $messageReply) { messageReply in ComposeMessageView.replyOrForwardMessage(messageReply: messageReply, mailboxManager: mailboxManager) } @@ -139,6 +135,14 @@ struct ThreadView: View { MoveEmailView.sheetView(mailboxManager: mailboxManager, from: folderId, moveHandler: handler) } } + .floatingPanel(item: $replyOrReplyAllThread) { thread in + ReplyActionsView( + mailboxManager: mailboxManager, + target: .threads([thread], false) + ) { message, replyMode in + messageReply = MessageReply(message: message, replyMode: replyMode) + } + } /*.floatingPanel(state: bottomSheet) { switch bottomSheet.state { case .contact(let recipient, let isRemote): @@ -199,7 +203,7 @@ struct ThreadView: View { case .reply: guard let message = thread.messages.last else { return } if message.canReplyAll { - bottomSheet.open(state: .replyOption(message, isThread: true)) + replyOrReplyAllThread = thread } else { messageReply = MessageReply(message: message, replyMode: .reply) } From aca3c6557e2028924f29997458e9133731dbf49b Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Thu, 27 Apr 2023 13:59:30 +0200 Subject: [PATCH 05/30] feat: ActionsPanelViewModifier --- Mail/Utils/NavigationStore.swift | 25 ++++++ .../Actions/ActionsPanelViewModifier.swift | 60 +++++++++++++++ .../Bottom sheets/Actions/ActionsView.swift | 9 +-- .../Actions/ActionsViewModel.swift | 7 +- .../Actions/ReplyActionsView.swift | 6 +- Mail/Views/Search/SearchView.swift | 19 +---- Mail/Views/SplitView.swift | 6 +- Mail/Views/Thread List/ThreadListView.swift | 20 ++--- Mail/Views/Thread/MessageHeaderView.swift | 20 ++--- Mail/Views/Thread/MoveEmailView.swift | 3 +- Mail/Views/Thread/ThreadView.swift | 77 ++++--------------- Mail/Views/ThreadListManagerView.swift | 4 - 12 files changed, 133 insertions(+), 123 deletions(-) create mode 100644 Mail/Utils/NavigationStore.swift create mode 100644 Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift diff --git a/Mail/Utils/NavigationStore.swift b/Mail/Utils/NavigationStore.swift new file mode 100644 index 000000000..e3f28a914 --- /dev/null +++ b/Mail/Utils/NavigationStore.swift @@ -0,0 +1,25 @@ +/* + 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 SwiftUI +import MailCore + +class NavigationStore: ObservableObject { + @Published var messageReply: MessageReply? +} diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift new file mode 100644 index 000000000..1966bc454 --- /dev/null +++ b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift @@ -0,0 +1,60 @@ +/* + 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 MailCore +import SwiftUI + +extension View { + func actionsPanel(actionsTarget: Binding, completionHandler: (() -> Void)? = nil) -> some View { + return modifier(ActionsPanelViewModifier(actionsTarget: actionsTarget, completionHandler: completionHandler)) + } +} + +struct ActionsPanelViewModifier: ViewModifier { + @EnvironmentObject private var mailboxManager: MailboxManager + @EnvironmentObject private var navigationStore: NavigationStore + + @StateObject private var moveSheet = MoveSheet() + + @Binding var actionsTarget: ActionsTarget? + var completionHandler: (() -> Void)? + + func body(content: Content) -> some View { + content + .floatingPanel(item: $actionsTarget) { target in + ActionsView(mailboxManager: mailboxManager, + target: target, + moveSheet: moveSheet) { message, replyMode in + // FIXME: There seems to be a bug where SwiftUI looses the "context" and attempts to present + // the view controller before waiting for the dismiss of the first one if we use a closure + // (this "fix" is temporary) + DispatchQueue.main.async { + navigationStore.messageReply = MessageReply(message: message, replyMode: replyMode) + } + } completionHandler: { + completionHandler?() + } + } + .sheet(isPresented: $moveSheet.isShowing) { + if case .move(let folderId, let handler) = moveSheet.state { + MoveEmailView.sheetView(from: folderId, moveHandler: handler) + } + } + } +} diff --git a/Mail/Views/Bottom sheets/Actions/ActionsView.swift b/Mail/Views/Bottom sheets/Actions/ActionsView.swift index dbba98846..f45f863c9 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsView.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsView.swift @@ -68,18 +68,13 @@ struct ActionsView: View { } } -/* struct ActionsView_Previews: PreviewProvider { static var previews: some View { ActionsView(mailboxManager: PreviewHelper.sampleMailboxManager, - target: .threads([PreviewHelper.sampleThread], false), - state: ThreadBottomSheet(), - globalSheet: GlobalBottomSheet(), - globalAlert: GlobalAlert()) { _, _ in /* Preview */ } + target: .threads([PreviewHelper.sampleThread], false)) { _, _ in /* Preview */ } .accentColor(AccentColor.pink.primary.swiftUIColor) } } -*/ struct QuickActionView: View { @Environment(\.dismiss) var dismiss @@ -120,6 +115,7 @@ struct QuickActionView: View { } struct ActionView: View { + @Environment(\.dismiss) var dismiss @ObservedObject var viewModel: ActionsViewModel let action: Action @@ -127,6 +123,7 @@ struct ActionView: View { var body: some View { Button { + dismiss() Task { await tryOrDisplayError { try await viewModel.didTap(action: action) diff --git a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift index 6dfdf505c..b0a6dd879 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift @@ -175,7 +175,12 @@ struct Action: Identifiable, Equatable { enum ActionsTarget: Equatable, Identifiable { var id: String { - return UUID().uuidString + switch self { + case .threads(let threads, let isMultiSelectionEnabled): + return threads.map {$0.id}.joined() + case .message(let message): + return message.uid + } } case threads([Thread], Bool) diff --git a/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift b/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift index ee2505794..ec4063920 100644 --- a/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift +++ b/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift @@ -27,10 +27,10 @@ struct ReplyActionsView: View { var quickActions: [Action] = [.reply, .replyAll] init(mailboxManager: MailboxManager, - target: ActionsTarget, + message: Message, replyHandler: @escaping (Message, ReplyMode) -> Void) { viewModel = ActionsViewModel(mailboxManager: mailboxManager, - target: target, + target: .message(message), matomoCategory: .replyBottomSheet, replyHandler: replyHandler) } @@ -52,7 +52,7 @@ struct ReplyActionsView: View { struct ReplyActionsView_Previews: PreviewProvider { static var previews: some View { ReplyActionsView(mailboxManager: PreviewHelper.sampleMailboxManager, - target: .threads([PreviewHelper.sampleThread], false)) { _, _ in /* Preview */ } + message: PreviewHelper.sampleMessage) { _, _ in /* Preview */ } .accentColor(AccentColor.pink.primary.swiftUIColor) } } diff --git a/Mail/Views/Search/SearchView.swift b/Mail/Views/Search/SearchView.swift index a4763a896..8b69b28e9 100644 --- a/Mail/Views/Search/SearchView.swift +++ b/Mail/Views/Search/SearchView.swift @@ -23,27 +23,19 @@ import RealmSwift import SwiftUI struct SearchView: View { - @StateObject var viewModel: SearchViewModel - @EnvironmentObject var splitViewManager: SplitViewManager - @EnvironmentObject var globalBottomSheet: GlobalBottomSheet - @StateObject var bottomSheet: ThreadBottomSheet + @StateObject var viewModel: SearchViewModel @Binding private var editedMessageDraft: Draft? - @Binding private var messageReply: MessageReply? let isCompact: Bool init(mailboxManager: MailboxManager, folder: Folder, editedMessageDraft: Binding, - messageReply: Binding, isCompact: Bool) { - let threadBottomSheet = ThreadBottomSheet() _editedMessageDraft = editedMessageDraft - _messageReply = messageReply - _bottomSheet = StateObject(wrappedValue: threadBottomSheet) _viewModel = StateObject(wrappedValue: SearchViewModel(mailboxManager: mailboxManager, folder: folder)) self.isCompact = isCompact } @@ -94,14 +86,6 @@ struct SearchView: View { .emptyState(isEmpty: viewModel.searchState == .noResults) { EmptyStateView.emptySearch } - .floatingPanel(state: bottomSheet, halfOpening: true) { - if case .actions(let target) = bottomSheet.state, !target.isInvalidated { - ActionsView(mailboxManager: viewModel.mailboxManager, - target: target) { message, replyMode in - messageReply = MessageReply(message: message, replyMode: replyMode) - } - } - } .refreshable { await viewModel.fetchThreads() } @@ -144,7 +128,6 @@ struct SearchView_Previews: PreviewProvider { SearchView(mailboxManager: PreviewHelper.sampleMailboxManager, folder: PreviewHelper.sampleFolder, editedMessageDraft: .constant(nil), - messageReply: .constant(nil), isCompact: true) } } diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index 72ed51fd2..cdf2e6456 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -65,6 +65,7 @@ struct SplitView: View { var mailboxManager: MailboxManager @State var splitViewController: UISplitViewController? @StateObject private var navigationDrawerController = NavigationDrawerState() + @StateObject private var navigationStore = NavigationStore() @Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.verticalSizeClass) var verticalSizeClass @@ -119,6 +120,9 @@ struct SplitView: View { } } } + .sheet(item: $navigationStore.messageReply) { messageReply in + ComposeMessageView.replyOrForwardMessage(messageReply: messageReply, mailboxManager: mailboxManager) + } .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in Task { try await mailboxManager.folders() @@ -149,7 +153,6 @@ struct SplitView: View { setupBehaviour(orientation: interfaceOrientation) splitViewController.preferredDisplayMode = .twoDisplaceSecondary } - .customAlert(isPresented: $alert.isShowing) { switch alert.state { case .reportPhishing(let message): @@ -167,6 +170,7 @@ struct SplitView: View { .environmentObject(navigationDrawerController) .environmentObject(bottomSheet) .environmentObject(alert) + .environmentObject(navigationStore) .defaultAppStorage(.shared) } diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index afba54167..02a843f4c 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -213,16 +213,9 @@ struct ThreadListView: View { matomo.track(eventWithCategory: .newMessage, name: "openFromFab") isShowingComposeNewMessageView.toggle() } - .floatingPanel(item: $viewModel.actionsTarget, content: { target in - ActionsView(mailboxManager: viewModel.mailboxManager, - target: target, - moveSheet: moveSheet) { message, replyMode in - viewModel.actionsTarget = nil - messageReply = MessageReply(message: message, replyMode: replyMode) - } completionHandler: { - multipleSelectionViewModel.isEnabled = false - } - }) + .actionsPanel(actionsTarget: $viewModel.actionsTarget) { + multipleSelectionViewModel.isEnabled = false + } .onAppear { networkMonitor.start() viewModel.globalBottomSheet = globalBottomSheet @@ -251,8 +244,8 @@ struct ThreadListView: View { ComposeMessageView.newMessage(mailboxManager: viewModel.mailboxManager) } .sheet(isPresented: $moveSheet.isShowing) { - if case let .move(folderId, handler) = moveSheet.state { - MoveEmailView.sheetView(mailboxManager: viewModel.mailboxManager, from: folderId, moveHandler: handler) + if case .move(let folderId, let handler) = moveSheet.state { + MoveEmailView.sheetView(from: folderId, moveHandler: handler) } } .customAlert(item: $flushAlert) { item in @@ -388,8 +381,7 @@ private struct ThreadListToolbar: ViewModifier { ToolbarButton(text: MailResourcesStrings.Localizable.buttonMore, icon: MailResourcesAsset.plusActions.swiftUIImage) { - bottomSheet - .open(state: .actions(.threads(Array(multipleSelectionViewModel.selectedItems), true))) + viewModel.actionsTarget = .threads(Array(multipleSelectionViewModel.selectedItems), true) } } .disabled(multipleSelectionViewModel.selectedItems.isEmpty) diff --git a/Mail/Views/Thread/MessageHeaderView.swift b/Mail/Views/Thread/MessageHeaderView.swift index 20feffa42..f44eda7b2 100644 --- a/Mail/Views/Thread/MessageHeaderView.swift +++ b/Mail/Views/Thread/MessageHeaderView.swift @@ -25,17 +25,18 @@ import RealmSwift import SwiftUI struct MessageHeaderView: View { + @EnvironmentObject private var navigationStore: NavigationStore + @State private var editedDraft: Draft? - @State private var messageReply: MessageReply? @State private var contactViewRecipient: Recipient? @State private var replyOrReplyAllMessage: Message? + @State private var actionsTarget: ActionsTarget? @ObservedRealmObject var message: Message @Binding var isHeaderExpanded: Bool @Binding var isMessageExpanded: Bool @EnvironmentObject var mailboxManager: MailboxManager - @EnvironmentObject var threadBottomSheet: ThreadBottomSheet @LazyInjectService private var matomo: MatomoUtils @@ -49,10 +50,10 @@ struct MessageHeaderView: View { if message.canReplyAll { replyOrReplyAllMessage = message } else { - messageReply = MessageReply(message: message, replyMode: .reply) + navigationStore.messageReply = MessageReply(message: message, replyMode: .reply) } } moreButtonTapped: { - threadBottomSheet.open(state: .actions(.message(message.thaw() ?? message))) + actionsTarget = .message(message) } recipientTapped: { recipient in contactViewRecipient = recipient } @@ -78,18 +79,13 @@ struct MessageHeaderView: View { .sheet(item: $editedDraft) { editedDraft in ComposeMessageView.editDraft(draft: editedDraft, mailboxManager: mailboxManager) } - .sheet(item: $messageReply) { messageReply in - ComposeMessageView.replyOrForwardMessage(messageReply: messageReply, mailboxManager: mailboxManager) - } .floatingPanel(item: $contactViewRecipient) { recipient in ContactActionsView(recipient: recipient) } + .actionsPanel(actionsTarget: $actionsTarget) .floatingPanel(item: $replyOrReplyAllMessage) { message in - ReplyActionsView( - mailboxManager: mailboxManager, - target: .message(message) - ) { message, replyMode in - messageReply = MessageReply(message: message, replyMode: replyMode) + ReplyActionsView(mailboxManager: mailboxManager, message: message) { message, replyMode in + navigationStore.messageReply = MessageReply(message: message, replyMode: replyMode) } } } diff --git a/Mail/Views/Thread/MoveEmailView.swift b/Mail/Views/Thread/MoveEmailView.swift index f2384bd2b..7e6e087a9 100644 --- a/Mail/Views/Thread/MoveEmailView.swift +++ b/Mail/Views/Thread/MoveEmailView.swift @@ -78,8 +78,7 @@ struct MoveEmailView: View { } extension MoveEmailView { - static func sheetView(mailboxManager: MailboxManager, from folderId: String?, - moveHandler: @escaping MoveEmailView.MoveHandler) -> some View { + static func sheetView(from folderId: String?, moveHandler: @escaping MoveEmailView.MoveHandler) -> some View { SheetView { MoveEmailView(currentFolderId: folderId, moveHandler: moveHandler) } diff --git a/Mail/Views/Thread/ThreadView.swift b/Mail/Views/Thread/ThreadView.swift index 4c35bdadf..ae9b5b66c 100644 --- a/Mail/Views/Thread/ThreadView.swift +++ b/Mail/Views/Thread/ThreadView.swift @@ -41,24 +41,22 @@ class MessageBottomSheet: DisplayedFloatingPanelState struct ThreadView: View { @EnvironmentObject private var splitViewManager: SplitViewManager - @Environment(\.mailNavigationPath) private var path @EnvironmentObject private var mailboxManager: MailboxManager + @EnvironmentObject private var navigationStore: NavigationStore + @EnvironmentObject var globalAlert: GlobalAlert + + @Environment(\.mailNavigationPath) private var path + @Environment(\.horizontalSizeClass) private var sizeClass + @Environment(\.verticalSizeClass) var verticalSizeClass + @Environment(\.dismiss) var dismiss @ObservedRealmObject var thread: Thread @State private var headerHeight: CGFloat = 0 @State private var displayNavigationTitle = false @State private var messageReply: MessageReply? - @State private var replyOrReplyAllThread: Thread? - - @StateObject private var moveSheet = MoveSheet() - - @State private var showEmptyView = false - - @EnvironmentObject var globalAlert: GlobalAlert - @Environment(\.horizontalSizeClass) private var sizeClass - @Environment(\.verticalSizeClass) private var verticalSizeClass - @Environment(\.dismiss) var dismiss + @State private var replyOrReplyAllMessage: Message? + @State private var actionsTarget: ActionsTarget? var isCompact: Bool { sizeClass == .compact || verticalSizeClass == .compact @@ -128,61 +126,16 @@ struct ThreadView: View { } ToolbarButton(text: MailResourcesStrings.Localizable.buttonMore, icon: MailResourcesAsset.plusActions.swiftUIImage) { - //threadBottomSheet.open(state: .actions(.threads([thread.thaw() ?? thread], false))) + actionsTarget = .threads([thread], false) } } } - .sheet(item: $messageReply) { messageReply in - ComposeMessageView.replyOrForwardMessage(messageReply: messageReply, mailboxManager: mailboxManager) - } - .sheet(isPresented: $moveSheet.isShowing) { - if case .move(let folderId, let handler) = moveSheet.state { - MoveEmailView.sheetView(mailboxManager: mailboxManager, from: folderId, moveHandler: handler) - } - } - .floatingPanel(item: $replyOrReplyAllThread) { thread in - ReplyActionsView( - mailboxManager: mailboxManager, - target: .threads([thread], false) - ) { message, replyMode in - messageReply = MessageReply(message: message, replyMode: replyMode) + .actionsPanel(actionsTarget: $actionsTarget) + .floatingPanel(item: $replyOrReplyAllMessage) { message in + ReplyActionsView(mailboxManager: mailboxManager, message: message) { message, replyMode in + navigationStore.messageReply = MessageReply(message: message, replyMode: replyMode) } } - /*.floatingPanel(state: bottomSheet) { - switch bottomSheet.state { - case .contact(let recipient, let isRemote): - ContactActionsView( - recipient: recipient, - isRemoteContact: isRemote, - bottomSheet: bottomSheet, - mailboxManager: mailboxManager - ) - case .replyOption(let message, let isThread): - ReplyActionsView( - mailboxManager: mailboxManager, - target: isThread ? .threads([thread], false) : .message(message), - state: threadBottomSheet, - globalSheet: globalBottomSheet - ) { message, replyMode in - bottomSheet.close() - messageReply = MessageReply(message: message, replyMode: replyMode) - } - case .none: - EmptyView() - } - }*/ - /*.floatingPanel(state: threadBottomSheet, halfOpening: true) { - if case .actions(let target) = threadBottomSheet.state, !target.isInvalidated { - ActionsView(mailboxManager: mailboxManager, - target: target, - state: threadBottomSheet, - globalSheet: globalBottomSheet, - globalAlert: globalAlert, - moveSheet: moveSheet) { message, replyMode in - messageReply = MessageReply(message: message, replyMode: replyMode) - } - } - }*/ .onChange(of: thread.messages) { newMessagesList in if newMessagesList.isEmpty || thread.messageInFolderCount == 0 { if isCompact { @@ -203,7 +156,7 @@ struct ThreadView: View { case .reply: guard let message = thread.messages.last else { return } if message.canReplyAll { - replyOrReplyAllThread = thread + replyOrReplyAllMessage = message } else { messageReply = MessageReply(message: message, replyMode: .reply) } diff --git a/Mail/Views/ThreadListManagerView.swift b/Mail/Views/ThreadListManagerView.swift index 9a872e0fb..6e663c797 100644 --- a/Mail/Views/ThreadListManagerView.swift +++ b/Mail/Views/ThreadListManagerView.swift @@ -48,7 +48,6 @@ struct ThreadListManagerView: View { mailboxManager: mailboxManager, folder: selectedFolder, editedMessageDraft: $editedMessageDraft, - messageReply: $messageReply, isCompact: isCompact ) } else { @@ -80,9 +79,6 @@ struct ThreadListManagerView: View { .sheet(item: $editedMessageDraft) { draft in ComposeMessageView.editDraft(draft: draft, mailboxManager: mailboxManager) } - .sheet(item: $messageReply) { messageReply in - ComposeMessageView.replyOrForwardMessage(messageReply: messageReply, mailboxManager: mailboxManager) - } } } From 1c49cd5d49c94298452417da0a9c7298aa8b224d Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Thu, 27 Apr 2023 15:26:47 +0200 Subject: [PATCH 06/30] refactor: Remove FloatingPanel lib related code --- .package.resolved | 27 ++- Mail/Helpers/BottomSheetState.swift | 34 ---- Mail/SceneDelegate.swift | 1 - Mail/Utils/FloatingPanelHelper.swift | 154 +----------------- .../Actions/ReportJunkView.swift | 2 - Mail/Views/Menu Drawer/MailboxQuotaView.swift | 2 - Mail/Views/SplitView.swift | 2 +- Mail/Views/Thread List/ThreadListCell.swift | 1 - .../Thread List/ThreadListSwipeAction.swift | 1 - Mail/Views/Thread List/ThreadListView.swift | 12 -- .../Thread List/ThreadListViewModel.swift | 3 - Project.swift | 2 - 12 files changed, 23 insertions(+), 218 deletions(-) delete mode 100644 Mail/Helpers/BottomSheetState.swift diff --git a/.package.resolved b/.package.resolved index 3a0482a0e..239dea020 100644 --- a/.package.resolved +++ b/.package.resolved @@ -27,15 +27,6 @@ "version" : "3.8.0" } }, - { - "identity" : "floatingpanel", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SCENEE/FloatingPanel", - "state" : { - "revision" : "2a29cb5b3ecf4beb67cf524a030dd74a11b956c4", - "version" : "2.6.1" - } - }, { "identity" : "ios-bug-tracker", "kind" : "remoteSourceControl", @@ -224,6 +215,15 @@ "version" : "1.5.2" } }, + { + "identity" : "swiftbackports", + "kind" : "remoteSourceControl", + "location" : "https://github.com/shaps80/SwiftBackports", + "state" : { + "revision" : "fafbeabf78b7e364abbbb7565cdfeee42af16211", + "version" : "1.0.2" + } + }, { "identity" : "swiftregex", "kind" : "remoteSourceControl", @@ -260,6 +260,15 @@ "version" : "1.3.0" } }, + { + "identity" : "swiftuibackports", + "kind" : "remoteSourceControl", + "location" : "https://github.com/shaps80/SwiftUIBackports", + "state" : { + "revision" : "556d42f391b74059a354b81b8c8e19cc7cb576f4", + "version" : "1.15.1" + } + }, { "identity" : "wrappinghstack", "kind" : "remoteSourceControl", diff --git a/Mail/Helpers/BottomSheetState.swift b/Mail/Helpers/BottomSheetState.swift deleted file mode 100644 index 42803c486..000000000 --- a/Mail/Helpers/BottomSheetState.swift +++ /dev/null @@ -1,34 +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 SwiftUI - -class BottomSheetState: ObservableObject { - @Published var isOpen = false - @Published private(set) var state: State? - - func open(state: State) { - self.state = state - isOpen = true - } - - func close() { - state = nil - isOpen = false - } -} diff --git a/Mail/SceneDelegate.swift b/Mail/SceneDelegate.swift index 58034ffd4..de2a6f8f4 100644 --- a/Mail/SceneDelegate.swift +++ b/Mail/SceneDelegate.swift @@ -101,7 +101,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, AccountManagerDelegate let view = view.environment(\.window, window) // Set root view controller let hostingController = UIHostingController(rootView: view) - FloatingPanelHelper.shared.attachToViewController(hostingController) setRootViewController(hostingController) } diff --git a/Mail/Utils/FloatingPanelHelper.swift b/Mail/Utils/FloatingPanelHelper.swift index 10150d8a2..5f385fc30 100644 --- a/Mail/Utils/FloatingPanelHelper.swift +++ b/Mail/Utils/FloatingPanelHelper.swift @@ -17,170 +17,24 @@ */ import Combine -import FloatingPanel import MailResources import SwiftUI import SwiftUIBackports -class AdaptiveDriveFloatingPanelController: FloatingPanelController { - private var contentSizeObservation: NSKeyValueObservation? - private let maxPanelWidth = 800.0 - var halfOpening = false - - deinit { - contentSizeObservation?.invalidate() - } - - init() { - super.init(delegate: nil) - let appearance = SurfaceAppearance() - appearance.cornerRadius = 20 - appearance.backgroundColor = MailResourcesAsset.backgroundSecondaryColor.color - surfaceView.appearance = appearance - surfaceView.grabberHandlePadding = 16 - surfaceView.grabberHandleSize = CGSize(width: 45, height: 5) - surfaceView.grabberHandle.barColor = MailResourcesAsset.elementsColor.color - surfaceView.contentPadding = UIEdgeInsets(top: 32, left: 0, bottom: 16, right: 0) - backdropView.dismissalTapGestureRecognizer.isEnabled = true - isRemovalInteractionEnabled = true - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) - updateMargins() - updateLayout(size: size) - } - - private func updateMargins() { - if view.frame.size.width > maxPanelWidth { - let insetWidth = view.frame.width - maxPanelWidth - surfaceView.containerMargins = UIEdgeInsets(top: 0, left: insetWidth / 2, bottom: 0, right: insetWidth / 2) - } else { - surfaceView.containerMargins = .zero - } - } - - func updateLayout(size: CGSize) { - guard let trackingScrollView = trackingScrollView else { return } - let fullHeight = min( - trackingScrollView.contentSize.height + surfaceView.contentPadding.top + surfaceView.contentPadding.bottom, - size.height - 96 - ) - let layout = AdaptiveFloatingPanelLayout( - height: fullHeight, - halfOpening: halfOpening && fullHeight > size.height / 2 - ) - self.layout = layout - invalidateLayout() - } - - func trackAndObserve(scrollView: UIScrollView) { - contentSizeObservation?.invalidate() - contentSizeObservation = scrollView.observe(\.contentSize, options: [.new, .old]) { [weak self] _, observedChanges in - // Do not update layout if the new value is the same as the old one (to fix a bug with collectionView) - guard observedChanges.newValue != observedChanges.oldValue, - let window = self?.view.window else { return } - self?.updateLayout(size: window.bounds.size) - } - track(scrollView: scrollView) - if let window = view.window { - updateMargins() - updateLayout(size: window.bounds.size) - } - } -} - -class AdaptiveFloatingPanelLayout: FloatingPanelLayout { - let position: FloatingPanelPosition = .bottom - let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] - let initialState: FloatingPanelState - - init(height: CGFloat, halfOpening: Bool) { - initialState = .half - if halfOpening { - anchors = [ - .half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea), - .full: FloatingPanelLayoutAnchor(absoluteInset: height, edge: .bottom, referenceGuide: .safeArea) - ] - } else { - anchors = [ - .half: FloatingPanelLayoutAnchor(absoluteInset: height, edge: .bottom, referenceGuide: .safeArea) - ] - } - } - - func backdropAlpha(for state: FloatingPanelState) -> CGFloat { - return 0.2 - } -} - -class DisplayedFloatingPanelState: ObservableObject, FloatingPanelControllerDelegate { +class DisplayedFloatingPanelState: ObservableObject { @Published var isOpen = false @Published private(set) var state: State? - private let floatingPanel: AdaptiveDriveFloatingPanelController - - init() { - floatingPanel = AdaptiveDriveFloatingPanelController() - floatingPanel.delegate = self - } - - func createPanelContent(content: Content, halfOpening: Bool) { - floatingPanel.halfOpening = halfOpening - let content = content.introspectScrollView { [weak self] scrollView in - self?.floatingPanel.trackAndObserve(scrollView: scrollView) - } - let viewController = UIHostingController(rootView: content) - viewController.view.backgroundColor = nil - floatingPanel.set(contentViewController: viewController) - } + init() {} func open(state: State) { - if let rootViewController = FloatingPanelHelper.shared.rootViewController { - rootViewController.present(floatingPanel, animated: true) - self.state = state - isOpen = true - } + self.state = state + isOpen = true } func close() { state = nil isOpen = false - floatingPanel.dismiss(animated: true) - } - - func floatingPanelDidRemove(_ fpc: FloatingPanelController) { - state = nil - isOpen = false - } -} - -class FloatingPanelHelper: FloatingPanelControllerDelegate { - static let shared = FloatingPanelHelper() - - private let sharedFloatingPanel = FloatingPanelController() - private(set) var rootViewController: UIViewController? - private init() { - // Protected constructor for singleton - } - - func attachToViewController(_ viewController: UIViewController) { - rootViewController = viewController - } -} - -extension View { - func floatingPanel(state: DisplayedFloatingPanelState, - halfOpening: Bool = false, - @ViewBuilder content: () -> Content) -> some View { - state.createPanelContent(content: ScrollView { content() }.defaultAppStorage(.shared), - halfOpening: halfOpening) - return self } } diff --git a/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift b/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift index ef47fd4ea..6d6dade2a 100644 --- a/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift +++ b/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift @@ -28,7 +28,6 @@ struct ReportJunkView: View { init(mailboxManager: MailboxManager, target: ActionsTarget, - state: ThreadBottomSheet, globalSheet: GlobalBottomSheet, globalAlert: GlobalAlert) { viewModel = ActionsViewModel(mailboxManager: mailboxManager, @@ -61,7 +60,6 @@ struct ReportJunkView_Previews: PreviewProvider { static var previews: some View { ReportJunkView(mailboxManager: PreviewHelper.sampleMailboxManager, target: .threads([PreviewHelper.sampleThread], false), - state: ThreadBottomSheet(), globalSheet: GlobalBottomSheet(), globalAlert: GlobalAlert()) .accentColor(AccentColor.pink.primary.swiftUIColor) diff --git a/Mail/Views/Menu Drawer/MailboxQuotaView.swift b/Mail/Views/Menu Drawer/MailboxQuotaView.swift index bed7bda6f..e0eba554c 100644 --- a/Mail/Views/Menu Drawer/MailboxQuotaView.swift +++ b/Mail/Views/Menu Drawer/MailboxQuotaView.swift @@ -22,8 +22,6 @@ import MailResources import SwiftUI struct MailboxQuotaView: View { - @EnvironmentObject var globalSheet: GlobalBottomSheet - let quotas: Quotas var progressString: String { return MailResourcesStrings.Localizable.menuDrawerMailboxStorage( diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index cdf2e6456..847241c5d 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -40,7 +40,7 @@ class GlobalBottomSheet: DisplayedFloatingPanelState { enum State { case getMoreStorage case restoreEmails - case reportJunk(threadBottomSheet: ThreadBottomSheet, target: ActionsTarget) + case reportJunk(target: ActionsTarget) } } diff --git a/Mail/Views/Thread List/ThreadListCell.swift b/Mail/Views/Thread List/ThreadListCell.swift index e791ba606..45f3296d2 100644 --- a/Mail/Views/Thread List/ThreadListCell.swift +++ b/Mail/Views/Thread List/ThreadListCell.swift @@ -106,7 +106,6 @@ struct ThreadListCell_Previews: PreviewProvider { thread: PreviewHelper.sampleThread, viewModel: ThreadListViewModel(mailboxManager: PreviewHelper.sampleMailboxManager, folder: PreviewHelper.sampleFolder, - bottomSheet: ThreadBottomSheet(), moveSheet: MoveSheet(), isCompact: false), multipleSelectionViewModel: ThreadListMultipleSelectionViewModel(mailboxManager: PreviewHelper.sampleMailboxManager), diff --git a/Mail/Views/Thread List/ThreadListSwipeAction.swift b/Mail/Views/Thread List/ThreadListSwipeAction.swift index 0876d41fe..a91a898b9 100644 --- a/Mail/Views/Thread List/ThreadListSwipeAction.swift +++ b/Mail/Views/Thread List/ThreadListSwipeAction.swift @@ -105,7 +105,6 @@ struct ThreadListSwipeAction_Previews: PreviewProvider { SwipeActionView(thread: PreviewHelper.sampleThread, viewModel: ThreadListViewModel(mailboxManager: PreviewHelper.sampleMailboxManager, folder: PreviewHelper.sampleFolder, - bottomSheet: ThreadBottomSheet(), moveSheet: MoveSheet(), isCompact: false), action: .delete) diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index 02a843f4c..9f3be67d4 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -24,12 +24,6 @@ import MailResources import RealmSwift import SwiftUI -class ThreadBottomSheet: DisplayedFloatingPanelState { - enum State: Equatable { - case actions(ActionsTarget) - } -} - class MoveSheet: SheetState { enum State { case move(folderId: String?, moveHandler: MoveEmailView.MoveHandler) @@ -59,7 +53,6 @@ struct ThreadListView: View { @AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor @State private var isShowingComposeNewMessageView = false - @StateObject var bottomSheet: ThreadBottomSheet @StateObject var moveSheet: MoveSheet @StateObject private var networkMonitor = NetworkMonitor() @Binding private var editedMessageDraft: Draft? @@ -86,15 +79,12 @@ struct ThreadListView: View { editedMessageDraft: Binding, messageReply: Binding, isCompact: Bool) { - let threadBottomSheet = ThreadBottomSheet() let moveEmailSheet = MoveSheet() _editedMessageDraft = editedMessageDraft _messageReply = messageReply - _bottomSheet = StateObject(wrappedValue: threadBottomSheet) _moveSheet = StateObject(wrappedValue: moveEmailSheet) _viewModel = StateObject(wrappedValue: ThreadListViewModel(mailboxManager: mailboxManager, folder: folder, - bottomSheet: threadBottomSheet, moveSheet: moveEmailSheet, isCompact: isCompact)) _multipleSelectionViewModel = @@ -200,7 +190,6 @@ struct ThreadListView: View { } .modifier(ThreadListToolbar(isCompact: isCompact, flushAlert: $flushAlert, - bottomSheet: bottomSheet, viewModel: viewModel, multipleSelectionViewModel: multipleSelectionViewModel) { withAnimation(.default.speed(2)) { @@ -284,7 +273,6 @@ private struct ThreadListToolbar: ViewModifier { @Binding var flushAlert: FlushAlertState? - @ObservedObject var bottomSheet: ThreadBottomSheet @ObservedObject var viewModel: ThreadListViewModel @ObservedObject var multipleSelectionViewModel: ThreadListMultipleSelectionViewModel diff --git a/Mail/Views/Thread List/ThreadListViewModel.swift b/Mail/Views/Thread List/ThreadListViewModel.swift index 6a5314b10..4dafd06ff 100644 --- a/Mail/Views/Thread List/ThreadListViewModel.swift +++ b/Mail/Views/Thread List/ThreadListViewModel.swift @@ -113,7 +113,6 @@ class DateSection: Identifiable { } let moveSheet: MoveSheet - let bottomSheet: ThreadBottomSheet var globalBottomSheet: GlobalBottomSheet? var scrollViewProxy: ScrollViewProxy? @@ -150,14 +149,12 @@ class DateSection: Identifiable { init( mailboxManager: MailboxManager, folder: Folder, - bottomSheet: ThreadBottomSheet, moveSheet: MoveSheet, isCompact: Bool ) { self.mailboxManager = mailboxManager self.folder = folder lastUpdate = folder.lastUpdate - self.bottomSheet = bottomSheet self.moveSheet = moveSheet self.isCompact = isCompact observeChanges() diff --git a/Project.swift b/Project.swift index bd2b85d0e..49c1905e0 100644 --- a/Project.swift +++ b/Project.swift @@ -44,7 +44,6 @@ let project = Project(name: "Mail", .package(url: "https://github.com/Ambrdctr/SQRichTextEditor", .branch("master")), .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: "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")), @@ -79,7 +78,6 @@ let project = Project(name: "Mail", .package(product: "SQRichTextEditor"), .package(product: "Shimmer"), .package(product: "WrappingHStack"), - .package(product: "FloatingPanel"), .package(product: "Lottie"), .package(product: "NavigationBackport"), .package(product: "Popovers"), From 046f9edbb6c866185ca852f1a128c6b61ca2abfa Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Thu, 27 Apr 2023 16:00:39 +0200 Subject: [PATCH 07/30] refactor(ActionsView): Reply - Use binding instead of closure --- .../Actions/ActionsPanelViewModifier.swift | 15 +++++++-------- .../Bottom sheets/Actions/ActionsView.swift | 7 +++---- .../Bottom sheets/Actions/ActionsViewModel.swift | 16 ++++++++++++---- .../Bottom sheets/Actions/ReplyActionsView.swift | 9 +++++---- Mail/Views/Thread/MessageHeaderView.swift | 4 +--- Mail/Views/Thread/ThreadView.swift | 9 +++------ 6 files changed, 31 insertions(+), 29 deletions(-) diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift index 1966bc454..f0bb6f7f4 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift @@ -33,6 +33,8 @@ struct ActionsPanelViewModifier: ViewModifier { @StateObject private var moveSheet = MoveSheet() @Binding var actionsTarget: ActionsTarget? + @State var reportDisplayProblemMessage: Message? + var completionHandler: (() -> Void)? func body(content: Content) -> some View { @@ -40,14 +42,8 @@ struct ActionsPanelViewModifier: ViewModifier { .floatingPanel(item: $actionsTarget) { target in ActionsView(mailboxManager: mailboxManager, target: target, - moveSheet: moveSheet) { message, replyMode in - // FIXME: There seems to be a bug where SwiftUI looses the "context" and attempts to present - // the view controller before waiting for the dismiss of the first one if we use a closure - // (this "fix" is temporary) - DispatchQueue.main.async { - navigationStore.messageReply = MessageReply(message: message, replyMode: replyMode) - } - } completionHandler: { + moveSheet: moveSheet, + messageReply: $navigationStore.messageReply) { completionHandler?() } } @@ -56,5 +52,8 @@ struct ActionsPanelViewModifier: ViewModifier { MoveEmailView.sheetView(from: folderId, moveHandler: handler) } } + .floatingPanel(item: $reportDisplayProblemMessage) { message in + ReportJunkView(mailboxManager: mailboxManager, target: .message(message)) + } } } diff --git a/Mail/Views/Bottom sheets/Actions/ActionsView.swift b/Mail/Views/Bottom sheets/Actions/ActionsView.swift index f45f863c9..e1a80b1fa 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsView.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsView.swift @@ -28,7 +28,7 @@ struct ActionsView: View { init(mailboxManager: MailboxManager, target: ActionsTarget, moveSheet: MoveSheet? = nil, - replyHandler: ((Message, ReplyMode) -> Void)? = nil, + messageReply: Binding? = nil, completionHandler: (() -> Void)? = nil) { var matomoCategory = MatomoUtils.EventCategory.bottomSheetMessageActions if case .threads = target { @@ -38,8 +38,8 @@ struct ActionsView: View { viewModel = ActionsViewModel(mailboxManager: mailboxManager, target: target, moveSheet: moveSheet, + messageReply: messageReply, matomoCategory: matomoCategory, - replyHandler: replyHandler, completionHandler: completionHandler) } @@ -70,8 +70,7 @@ struct ActionsView: View { struct ActionsView_Previews: PreviewProvider { static var previews: some View { - ActionsView(mailboxManager: PreviewHelper.sampleMailboxManager, - target: .threads([PreviewHelper.sampleThread], false)) { _, _ in /* Preview */ } + ActionsView(mailboxManager: PreviewHelper.sampleMailboxManager, target: .threads([PreviewHelper.sampleThread], false)) .accentColor(AccentColor.pink.primary.swiftUIColor) } } diff --git a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift index b0a6dd879..c4e7fa82c 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift @@ -378,14 +378,22 @@ enum ActionsTarget: Equatable, Identifiable { } private func reply(mode: ReplyMode) async throws { + var displayedMessageReply: MessageReply? switch target { - case let .threads(threads, _): + case .threads(let threads, _): // We don't handle this action in multiple selection guard threads.count == 1, let thread = threads.first, let message = thread.messages.last(where: { !$0.isDraft }) else { break } - replyHandler?(message, mode) - case let .message(message): - replyHandler?(message, mode) + displayedMessageReply = MessageReply(message: message, replyMode: mode) + case .message(let message): + displayedMessageReply = MessageReply(message: message, replyMode: mode) + } + + // FIXME: There seems to be a bug where SwiftUI looses the "context" and attempts to present + // the view controller before waiting for the dismiss of the first one if we use a closure + // (this "fix" is temporary) + DispatchQueue.main.async { + self.messageReply?.wrappedValue = displayedMessageReply } } diff --git a/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift b/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift index ec4063920..404f1a32e 100644 --- a/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift +++ b/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift @@ -28,11 +28,11 @@ struct ReplyActionsView: View { init(mailboxManager: MailboxManager, message: Message, - replyHandler: @escaping (Message, ReplyMode) -> Void) { + messageReply: Binding?) { viewModel = ActionsViewModel(mailboxManager: mailboxManager, target: .message(message), - matomoCategory: .replyBottomSheet, - replyHandler: replyHandler) + messageReply: messageReply, + matomoCategory: .replyBottomSheet) } var body: some View { @@ -52,7 +52,8 @@ struct ReplyActionsView: View { struct ReplyActionsView_Previews: PreviewProvider { static var previews: some View { ReplyActionsView(mailboxManager: PreviewHelper.sampleMailboxManager, - message: PreviewHelper.sampleMessage) { _, _ in /* Preview */ } + message: PreviewHelper.sampleMessage, + messageReply: nil) .accentColor(AccentColor.pink.primary.swiftUIColor) } } diff --git a/Mail/Views/Thread/MessageHeaderView.swift b/Mail/Views/Thread/MessageHeaderView.swift index f44eda7b2..8113fe859 100644 --- a/Mail/Views/Thread/MessageHeaderView.swift +++ b/Mail/Views/Thread/MessageHeaderView.swift @@ -84,9 +84,7 @@ struct MessageHeaderView: View { } .actionsPanel(actionsTarget: $actionsTarget) .floatingPanel(item: $replyOrReplyAllMessage) { message in - ReplyActionsView(mailboxManager: mailboxManager, message: message) { message, replyMode in - navigationStore.messageReply = MessageReply(message: message, replyMode: replyMode) - } + ReplyActionsView(mailboxManager: mailboxManager, message: message, messageReply: $navigationStore.messageReply) } } diff --git a/Mail/Views/Thread/ThreadView.swift b/Mail/Views/Thread/ThreadView.swift index ae9b5b66c..f7be92e0d 100644 --- a/Mail/Views/Thread/ThreadView.swift +++ b/Mail/Views/Thread/ThreadView.swift @@ -54,7 +54,6 @@ struct ThreadView: View { @State private var headerHeight: CGFloat = 0 @State private var displayNavigationTitle = false - @State private var messageReply: MessageReply? @State private var replyOrReplyAllMessage: Message? @State private var actionsTarget: ActionsTarget? @@ -132,9 +131,7 @@ struct ThreadView: View { } .actionsPanel(actionsTarget: $actionsTarget) .floatingPanel(item: $replyOrReplyAllMessage) { message in - ReplyActionsView(mailboxManager: mailboxManager, message: message) { message, replyMode in - navigationStore.messageReply = MessageReply(message: message, replyMode: replyMode) - } + ReplyActionsView(mailboxManager: mailboxManager, message: message, messageReply: $navigationStore.messageReply) } .onChange(of: thread.messages) { newMessagesList in if newMessagesList.isEmpty || thread.messageInFolderCount == 0 { @@ -158,11 +155,11 @@ struct ThreadView: View { if message.canReplyAll { replyOrReplyAllMessage = message } else { - messageReply = MessageReply(message: message, replyMode: .reply) + navigationStore.messageReply = MessageReply(message: message, replyMode: .reply) } case .forward: guard let message = thread.messages.last else { return } - messageReply = MessageReply(message: message, replyMode: .forward) + navigationStore.messageReply = MessageReply(message: message, replyMode: .forward) case .archive: Task { await tryOrDisplayError { From ca9e27ca2f37b91531d855f29b8b90890f7e9a77 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Thu, 27 Apr 2023 16:15:08 +0200 Subject: [PATCH 08/30] refactor: ReportJunkView native floatingPanel --- .../Actions/ActionsPanelViewModifier.swift | 9 +++++---- Mail/Views/Bottom sheets/Actions/ActionsView.swift | 2 ++ .../Bottom sheets/Actions/ActionsViewModel.swift | 11 +++++++---- Mail/Views/Bottom sheets/Actions/ReportJunkView.swift | 9 ++------- Mail/Views/SplitView.swift | 2 -- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift index f0bb6f7f4..1679db34c 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift @@ -33,7 +33,7 @@ struct ActionsPanelViewModifier: ViewModifier { @StateObject private var moveSheet = MoveSheet() @Binding var actionsTarget: ActionsTarget? - @State var reportDisplayProblemMessage: Message? + @State var reportJunkActionsTarget: ActionsTarget? var completionHandler: (() -> Void)? @@ -43,7 +43,8 @@ struct ActionsPanelViewModifier: ViewModifier { ActionsView(mailboxManager: mailboxManager, target: target, moveSheet: moveSheet, - messageReply: $navigationStore.messageReply) { + messageReply: $navigationStore.messageReply, + reportJunkActionsTarget: $reportJunkActionsTarget) { completionHandler?() } } @@ -52,8 +53,8 @@ struct ActionsPanelViewModifier: ViewModifier { MoveEmailView.sheetView(from: folderId, moveHandler: handler) } } - .floatingPanel(item: $reportDisplayProblemMessage) { message in - ReportJunkView(mailboxManager: mailboxManager, target: .message(message)) + .floatingPanel(item: $reportJunkActionsTarget) { target in + ReportJunkView(mailboxManager: mailboxManager, target: target) } } } diff --git a/Mail/Views/Bottom sheets/Actions/ActionsView.swift b/Mail/Views/Bottom sheets/Actions/ActionsView.swift index e1a80b1fa..e0f20a961 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsView.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsView.swift @@ -29,6 +29,7 @@ struct ActionsView: View { target: ActionsTarget, moveSheet: MoveSheet? = nil, messageReply: Binding? = nil, + reportJunkActionsTarget: Binding? = nil, completionHandler: (() -> Void)? = nil) { var matomoCategory = MatomoUtils.EventCategory.bottomSheetMessageActions if case .threads = target { @@ -39,6 +40,7 @@ struct ActionsView: View { target: target, moveSheet: moveSheet, messageReply: messageReply, + reportJunkActionsTarget: reportJunkActionsTarget, matomoCategory: matomoCategory, completionHandler: completionHandler) } diff --git a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift index c4e7fa82c..cf66d65c3 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift @@ -209,7 +209,8 @@ enum ActionsTarget: Equatable, Identifiable { private let mailboxManager: MailboxManager private let target: ActionsTarget private let moveSheet: MoveSheet? - private let replyHandler: ((Message, ReplyMode) -> Void)? + private let messageReply: Binding? + private let reportJunkActionsTarget: Binding? private let completionHandler: (() -> Void)? private let matomoCategory: MatomoUtils.EventCategory? @@ -222,13 +223,15 @@ enum ActionsTarget: Equatable, Identifiable { init(mailboxManager: MailboxManager, target: ActionsTarget, moveSheet: MoveSheet? = nil, + messageReply: Binding? = nil, + reportJunkActionsTarget: Binding? = nil, matomoCategory: MatomoUtils.EventCategory? = nil, - replyHandler: ((Message, ReplyMode) -> Void)? = nil, completionHandler: (() -> Void)? = nil) { self.mailboxManager = mailboxManager self.target = target.freeze() self.moveSheet = moveSheet - self.replyHandler = replyHandler + self.messageReply = messageReply + self.reportJunkActionsTarget = reportJunkActionsTarget self.completionHandler = completionHandler self.matomoCategory = matomoCategory setActions() @@ -445,7 +448,7 @@ enum ActionsTarget: Equatable, Identifiable { } private func displayReportJunk() { - //globalSheet.open(state: .reportJunk(threadBottomSheet: state, target: target)) + reportJunkActionsTarget?.wrappedValue = target } private func block() async throws { diff --git a/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift b/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift index 6d6dade2a..48d124e6e 100644 --- a/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift +++ b/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift @@ -27,9 +27,7 @@ struct ReportJunkView: View { var actions: [Action] = [] init(mailboxManager: MailboxManager, - target: ActionsTarget, - globalSheet: GlobalBottomSheet, - globalAlert: GlobalAlert) { + target: ActionsTarget) { viewModel = ActionsViewModel(mailboxManager: mailboxManager, target: target) if case .message(let message) = target { @@ -58,10 +56,7 @@ struct ReportJunkView: View { struct ReportJunkView_Previews: PreviewProvider { static var previews: some View { - ReportJunkView(mailboxManager: PreviewHelper.sampleMailboxManager, - target: .threads([PreviewHelper.sampleThread], false), - globalSheet: GlobalBottomSheet(), - globalAlert: GlobalAlert()) + ReportJunkView(mailboxManager: PreviewHelper.sampleMailboxManager, target: .message(PreviewHelper.sampleMessage)) .accentColor(AccentColor.pink.primary.swiftUIColor) } } diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index 847241c5d..7aac8a99b 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -38,9 +38,7 @@ extension EnvironmentValues { class GlobalBottomSheet: DisplayedFloatingPanelState { enum State { - case getMoreStorage case restoreEmails - case reportJunk(target: ActionsTarget) } } From 504f1d816d641d9ca10163a75d0b13760a734d34 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Thu, 27 Apr 2023 16:29:42 +0200 Subject: [PATCH 09/30] refactor: Remove GlobalBottomSheet --- Mail/Views/Bottom sheets/RestoreEmailsView.swift | 7 ++++--- Mail/Views/Menu Drawer/MenuDrawerView.swift | 8 +++----- Mail/Views/Menu Drawer/MenuDrawerViewModel.swift | 10 +++++----- Mail/Views/SplitView.swift | 8 -------- Mail/Views/Thread List/ThreadListView.swift | 2 -- Mail/Views/Thread List/ThreadListViewModel.swift | 2 -- 6 files changed, 12 insertions(+), 25 deletions(-) diff --git a/Mail/Views/Bottom sheets/RestoreEmailsView.swift b/Mail/Views/Bottom sheets/RestoreEmailsView.swift index efc3ee1ae..0a7ff7de9 100644 --- a/Mail/Views/Bottom sheets/RestoreEmailsView.swift +++ b/Mail/Views/Bottom sheets/RestoreEmailsView.swift @@ -24,6 +24,8 @@ import MailResources import SwiftUI struct RestoreEmailsView: View { + @EnvironmentObject var mailboxManager: MailboxManager + @State private var selectedDate = "" @State private var availableDates = [String]() @@ -31,8 +33,6 @@ struct RestoreEmailsView: View { @LazyInjectService private var matomo: MatomoUtils - let mailboxManager: MailboxManager - var body: some View { VStack(alignment: .leading) { Text(MailResourcesStrings.Localizable.restoreEmailsTitle) @@ -94,6 +94,7 @@ struct RestoreEmailsView: View { struct RestoreEmailsView_Previews: PreviewProvider { static var previews: some View { - RestoreEmailsView(mailboxManager: PreviewHelper.sampleMailboxManager) + RestoreEmailsView() + .environmentObject(PreviewHelper.sampleMailboxManager) } } diff --git a/Mail/Views/Menu Drawer/MenuDrawerView.swift b/Mail/Views/Menu Drawer/MenuDrawerView.swift index 9fad5c2cd..3d39b4a87 100644 --- a/Mail/Views/Menu Drawer/MenuDrawerView.swift +++ b/Mail/Views/Menu Drawer/MenuDrawerView.swift @@ -129,7 +129,6 @@ struct NavigationDrawer: View { struct MenuDrawerView: View { @EnvironmentObject var splitViewManager: SplitViewManager - @EnvironmentObject var bottomSheet: GlobalBottomSheet @StateObject var viewModel: MenuDrawerViewModel @@ -192,9 +191,6 @@ struct MenuDrawerView: View { } .background(MailResourcesAsset.backgroundSecondaryColor.swiftUIColor.ignoresSafeArea()) .environment(\.folderCellType, .link) - .onAppear { - viewModel.createMenuItems(bottomSheet: bottomSheet) - } .sheet(isPresented: $viewModel.isShowingHelp) { SheetView { HelpView() @@ -203,6 +199,9 @@ struct MenuDrawerView: View { .sheet(isPresented: $viewModel.isShowingBugTracker) { BugTrackerView(isPresented: $viewModel.isShowingBugTracker) } + .floatingPanel(isPresented: $viewModel.isShowingRestoreMails) { + RestoreEmailsView() + } } } @@ -217,6 +216,5 @@ struct MenuDrawerView_Previews: PreviewProvider { static var previews: some View { MenuDrawerView(mailboxManager: PreviewHelper.sampleMailboxManager, isCompact: false) .environmentObject(NavigationDrawerState()) - .environmentObject(GlobalBottomSheet()) } } diff --git a/Mail/Views/Menu Drawer/MenuDrawerViewModel.swift b/Mail/Views/Menu Drawer/MenuDrawerViewModel.swift index d523c65c6..ae1a519f7 100644 --- a/Mail/Views/Menu Drawer/MenuDrawerViewModel.swift +++ b/Mail/Views/Menu Drawer/MenuDrawerViewModel.swift @@ -60,8 +60,8 @@ class MenuDrawerViewModel: ObservableObject { @Published var actionsMenuItems = [MenuItem]() @Published var isShowingHelp = false @Published var isShowingBugTracker = false + @Published var isShowingRestoreMails = false - private var bottomSheet: GlobalBottomSheet? private var foldersObservationToken: NotificationToken? private var mailboxesObservationToken: NotificationToken? @@ -103,6 +103,8 @@ class MenuDrawerViewModel: ObservableObject { break } } + + createMenuItems() } private func handleFoldersUpdate(_ folders: Results) { @@ -110,9 +112,7 @@ class MenuDrawerViewModel: ObservableObject { userFolders = NestableFolder.createFoldersHierarchy(from: Array(folders.where { $0.role == nil })) } - func createMenuItems(bottomSheet: GlobalBottomSheet) { - self.bottomSheet = bottomSheet - + func createMenuItems() { helpMenuItems = [ MenuItem(icon: MailResourcesAsset.feedback, label: MailResourcesStrings.Localizable.buttonFeedback, @@ -159,6 +159,6 @@ class MenuDrawerViewModel: ObservableObject { } private func restoreMails() { - bottomSheet?.open(state: .restoreEmails) + isShowingRestoreMails = true } } diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index 7aac8a99b..97a56b457 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -36,12 +36,6 @@ extension EnvironmentValues { } } -class GlobalBottomSheet: DisplayedFloatingPanelState { - enum State { - case restoreEmails - } -} - class GlobalAlert: SheetState { enum State { case reportPhishing(message: Message) @@ -69,7 +63,6 @@ struct SplitView: View { @Environment(\.verticalSizeClass) var verticalSizeClass @Environment(\.window) var window - @StateObject private var bottomSheet = GlobalBottomSheet() @StateObject private var alert = GlobalAlert() @StateObject private var splitViewManager: SplitViewManager @@ -166,7 +159,6 @@ struct SplitView: View { .environmentObject(mailboxManager) .environmentObject(splitViewManager) .environmentObject(navigationDrawerController) - .environmentObject(bottomSheet) .environmentObject(alert) .environmentObject(navigationStore) .defaultAppStorage(.shared) diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index 9f3be67d4..269cc7600 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -46,7 +46,6 @@ struct ThreadListView: View { @StateObject var multipleSelectionViewModel: ThreadListMultipleSelectionViewModel @EnvironmentObject var splitViewManager: SplitViewManager - @EnvironmentObject var globalBottomSheet: GlobalBottomSheet @Environment(\.mailNavigationPath) private var path @AppStorage(UserDefaults.shared.key(.threadDensity)) private var threadDensity = DefaultPreferences.threadDensity @@ -207,7 +206,6 @@ struct ThreadListView: View { } .onAppear { networkMonitor.start() - viewModel.globalBottomSheet = globalBottomSheet viewModel.selectedThread = nil } .onChange(of: splitViewManager.selectedFolder) { newFolder in diff --git a/Mail/Views/Thread List/ThreadListViewModel.swift b/Mail/Views/Thread List/ThreadListViewModel.swift index 4dafd06ff..01ad628e0 100644 --- a/Mail/Views/Thread List/ThreadListViewModel.swift +++ b/Mail/Views/Thread List/ThreadListViewModel.swift @@ -101,7 +101,6 @@ class DateSection: Identifiable { @Published var lastUpdate: Date? @Published var actionsTarget: ActionsTarget? - // Used to know thread location private var selectedThreadIndex: Int? var filteredThreads = [Thread]() { @@ -113,7 +112,6 @@ class DateSection: Identifiable { } let moveSheet: MoveSheet - var globalBottomSheet: GlobalBottomSheet? var scrollViewProxy: ScrollViewProxy? var isCompact: Bool From 0c38d2a085b34c4f405840f45e36dd69460da41a Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Thu, 27 Apr 2023 16:53:54 +0200 Subject: [PATCH 10/30] refactor: ReportJunkView native model --- .../Actions/ActionsPanelViewModifier.swift | 8 +++++++- .../Bottom sheets/Actions/ActionsViewModel.swift | 13 +++++++++---- .../Bottom sheets/Actions/ReportJunkView.swift | 10 +++++++--- Mail/Views/SplitView.swift | 3 --- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift index 1679db34c..b8abd07d0 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift @@ -34,6 +34,7 @@ struct ActionsPanelViewModifier: ViewModifier { @Binding var actionsTarget: ActionsTarget? @State var reportJunkActionsTarget: ActionsTarget? + @State var reportedForPhishingMessage: Message? var completionHandler: (() -> Void)? @@ -54,7 +55,12 @@ struct ActionsPanelViewModifier: ViewModifier { } } .floatingPanel(item: $reportJunkActionsTarget) { target in - ReportJunkView(mailboxManager: mailboxManager, target: target) + ReportJunkView(mailboxManager: mailboxManager, + target: target, + reportedForPhishingMessage: $reportedForPhishingMessage) + } + .customAlert(item: $reportedForPhishingMessage) { message in + ReportPhishingView(message: message) } } } diff --git a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift index cf66d65c3..8163c72a2 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift @@ -211,6 +211,7 @@ enum ActionsTarget: Equatable, Identifiable { private let moveSheet: MoveSheet? private let messageReply: Binding? private let reportJunkActionsTarget: Binding? + private let reportedForPhishingMessage: Binding? private let completionHandler: (() -> Void)? private let matomoCategory: MatomoUtils.EventCategory? @@ -225,6 +226,7 @@ enum ActionsTarget: Equatable, Identifiable { moveSheet: MoveSheet? = nil, messageReply: Binding? = nil, reportJunkActionsTarget: Binding? = nil, + reportedForPhishingMessage: Binding? = nil, matomoCategory: MatomoUtils.EventCategory? = nil, completionHandler: (() -> Void)? = nil) { self.mailboxManager = mailboxManager @@ -232,6 +234,7 @@ enum ActionsTarget: Equatable, Identifiable { self.moveSheet = moveSheet self.messageReply = messageReply self.reportJunkActionsTarget = reportJunkActionsTarget + self.reportedForPhishingMessage = reportedForPhishingMessage self.completionHandler = completionHandler self.matomoCategory = matomoCategory setActions() @@ -395,8 +398,8 @@ enum ActionsTarget: Equatable, Identifiable { // FIXME: There seems to be a bug where SwiftUI looses the "context" and attempts to present // the view controller before waiting for the dismiss of the first one if we use a closure // (this "fix" is temporary) - DispatchQueue.main.async { - self.messageReply?.wrappedValue = displayedMessageReply + DispatchQueue.main.async { [weak self] in + self?.messageReply?.wrappedValue = displayedMessageReply } } @@ -462,8 +465,10 @@ enum ActionsTarget: Equatable, Identifiable { private func phishing() async throws { // This action is only available on a single message - guard case let .message(message) = target else { return } - // globalAlert?.state = .reportPhishing(message: message) + guard case .message(let message) = target else { return } + DispatchQueue.main.async { [weak self] in + self?.reportedForPhishingMessage?.wrappedValue = message + } } private func printAction() { diff --git a/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift b/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift index 48d124e6e..5f5c7f053 100644 --- a/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift +++ b/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift @@ -27,9 +27,11 @@ struct ReportJunkView: View { var actions: [Action] = [] init(mailboxManager: MailboxManager, - target: ActionsTarget) { + target: ActionsTarget, + reportedForPhishingMessage: Binding) { viewModel = ActionsViewModel(mailboxManager: mailboxManager, - target: target) + target: target, + reportedForPhishingMessage: reportedForPhishingMessage) if case .message(let message) = target { let spam = message.folder?.role == .spam actions.append(contentsOf: [ @@ -56,7 +58,9 @@ struct ReportJunkView: View { struct ReportJunkView_Previews: PreviewProvider { static var previews: some View { - ReportJunkView(mailboxManager: PreviewHelper.sampleMailboxManager, target: .message(PreviewHelper.sampleMessage)) + ReportJunkView(mailboxManager: PreviewHelper.sampleMailboxManager, + target: .message(PreviewHelper.sampleMessage), + reportedForPhishingMessage: .constant(nil)) .accentColor(AccentColor.pink.primary.swiftUIColor) } } diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index 97a56b457..209eded09 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -38,7 +38,6 @@ extension EnvironmentValues { class GlobalAlert: SheetState { enum State { - case reportPhishing(message: Message) case reportDisplayProblem(message: Message) } } @@ -146,8 +145,6 @@ struct SplitView: View { } .customAlert(isPresented: $alert.isShowing) { switch alert.state { - case .reportPhishing(let message): - ReportPhishingView(message: message) case .reportDisplayProblem(let message): ReportDisplayProblemView(message: message) case .none: From 341dc27b9f2cecfac46e0b5342ebf07f72cfd18c Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Thu, 27 Apr 2023 17:03:18 +0200 Subject: [PATCH 11/30] refactor: Reconnect ReportDisplayProblemView to panel --- .../Actions/ActionsPanelViewModifier.swift | 7 ++++++- .../Bottom sheets/Actions/ActionsView.swift | 2 ++ .../Actions/ActionsViewModel.swift | 10 ++++++---- Mail/Views/SplitView.swift | 17 ----------------- Mail/Views/Thread/ThreadView.swift | 1 - 5 files changed, 14 insertions(+), 23 deletions(-) diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift index b8abd07d0..274a8ebd3 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift @@ -35,6 +35,7 @@ struct ActionsPanelViewModifier: ViewModifier { @Binding var actionsTarget: ActionsTarget? @State var reportJunkActionsTarget: ActionsTarget? @State var reportedForPhishingMessage: Message? + @State var reportedForDisplayProblemMessage: Message? var completionHandler: (() -> Void)? @@ -45,7 +46,8 @@ struct ActionsPanelViewModifier: ViewModifier { target: target, moveSheet: moveSheet, messageReply: $navigationStore.messageReply, - reportJunkActionsTarget: $reportJunkActionsTarget) { + reportJunkActionsTarget: $reportJunkActionsTarget, + reportedForDisplayProblemMessage: $reportedForDisplayProblemMessage) { completionHandler?() } } @@ -62,5 +64,8 @@ struct ActionsPanelViewModifier: ViewModifier { .customAlert(item: $reportedForPhishingMessage) { message in ReportPhishingView(message: message) } + .customAlert(item: $reportedForDisplayProblemMessage) { message in + ReportDisplayProblemView(message: message) + } } } diff --git a/Mail/Views/Bottom sheets/Actions/ActionsView.swift b/Mail/Views/Bottom sheets/Actions/ActionsView.swift index e0f20a961..5f5971ef6 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsView.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsView.swift @@ -30,6 +30,7 @@ struct ActionsView: View { moveSheet: MoveSheet? = nil, messageReply: Binding? = nil, reportJunkActionsTarget: Binding? = nil, + reportedForDisplayProblemMessage: Binding? = nil, completionHandler: (() -> Void)? = nil) { var matomoCategory = MatomoUtils.EventCategory.bottomSheetMessageActions if case .threads = target { @@ -41,6 +42,7 @@ struct ActionsView: View { moveSheet: moveSheet, messageReply: messageReply, reportJunkActionsTarget: reportJunkActionsTarget, + reportedForDisplayProblemMessage: reportedForDisplayProblemMessage, matomoCategory: matomoCategory, completionHandler: completionHandler) } diff --git a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift index 8163c72a2..4365d1041 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift @@ -227,6 +227,7 @@ enum ActionsTarget: Equatable, Identifiable { messageReply: Binding? = nil, reportJunkActionsTarget: Binding? = nil, reportedForPhishingMessage: Binding? = nil, + reportedForDisplayProblemMessage: Binding? = nil, matomoCategory: MatomoUtils.EventCategory? = nil, completionHandler: (() -> Void)? = nil) { self.mailboxManager = mailboxManager @@ -235,6 +236,7 @@ enum ActionsTarget: Equatable, Identifiable { self.messageReply = messageReply self.reportJunkActionsTarget = reportJunkActionsTarget self.reportedForPhishingMessage = reportedForPhishingMessage + self.reportedForDisplayProblemMessage = reportedForDisplayProblemMessage self.completionHandler = completionHandler self.matomoCategory = matomoCategory setActions() @@ -334,7 +336,7 @@ enum ActionsTarget: Equatable, Identifiable { case .print: printAction() case .report: - report() + reportDisplayProblem() case .editMenu: editMenu() case .moveToInbox: @@ -476,10 +478,10 @@ enum ActionsTarget: Equatable, Identifiable { showWorkInProgressSnackBar() } - private func report() { + private func reportDisplayProblem() { // This action is only available on a single message - guard case let .message(message) = target else { return } - // globalAlert?.state = .reportDisplayProblem(message: message) + guard case .message(let message) = target else { return } + reportedForDisplayProblemMessage?.wrappedValue = message } private func editMenu() { diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index 209eded09..5c47cbdcb 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -36,12 +36,6 @@ extension EnvironmentValues { } } -class GlobalAlert: SheetState { - enum State { - case reportDisplayProblem(message: Message) - } -} - public class SplitViewManager: ObservableObject { @Published var showSearch = false @Published var selectedFolder: Folder? @@ -62,8 +56,6 @@ struct SplitView: View { @Environment(\.verticalSizeClass) var verticalSizeClass @Environment(\.window) var window - @StateObject private var alert = GlobalAlert() - @StateObject private var splitViewManager: SplitViewManager @State private var path = [Thread]() @@ -143,20 +135,11 @@ struct SplitView: View { setupBehaviour(orientation: interfaceOrientation) splitViewController.preferredDisplayMode = .twoDisplaceSecondary } - .customAlert(isPresented: $alert.isShowing) { - switch alert.state { - case .reportDisplayProblem(let message): - ReportDisplayProblemView(message: message) - case .none: - EmptyView() - } - } .environment(\.mailNavigationPath, $path) .environment(\.realmConfiguration, mailboxManager.realmConfiguration) .environmentObject(mailboxManager) .environmentObject(splitViewManager) .environmentObject(navigationDrawerController) - .environmentObject(alert) .environmentObject(navigationStore) .defaultAppStorage(.shared) } diff --git a/Mail/Views/Thread/ThreadView.swift b/Mail/Views/Thread/ThreadView.swift index f7be92e0d..4c8190f1b 100644 --- a/Mail/Views/Thread/ThreadView.swift +++ b/Mail/Views/Thread/ThreadView.swift @@ -43,7 +43,6 @@ struct ThreadView: View { @EnvironmentObject private var splitViewManager: SplitViewManager @EnvironmentObject private var mailboxManager: MailboxManager @EnvironmentObject private var navigationStore: NavigationStore - @EnvironmentObject var globalAlert: GlobalAlert @Environment(\.mailNavigationPath) private var path @Environment(\.horizontalSizeClass) private var sizeClass From 5747102e78fa5ba14cb1960029cffb9368d320a2 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Thu, 27 Apr 2023 17:04:52 +0200 Subject: [PATCH 12/30] refactor: Remove useless code -> DisplayedFloatingPanelState --- Mail/Utils/FloatingPanelHelper.swift | 17 ----------------- .../Actions/ActionsViewModel.swift | 1 + Mail/Views/Thread/ThreadView.swift | 7 ------- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/Mail/Utils/FloatingPanelHelper.swift b/Mail/Utils/FloatingPanelHelper.swift index 5f385fc30..2da9c3a07 100644 --- a/Mail/Utils/FloatingPanelHelper.swift +++ b/Mail/Utils/FloatingPanelHelper.swift @@ -21,23 +21,6 @@ import MailResources import SwiftUI import SwiftUIBackports -class DisplayedFloatingPanelState: ObservableObject { - @Published var isOpen = false - @Published private(set) var state: State? - - init() {} - - func open(state: State) { - self.state = state - isOpen = true - } - - func close() { - state = nil - isOpen = false - } -} - extension View { func floatingPanel(isPresented: Binding, @ViewBuilder content: @escaping () -> Content) -> some View { diff --git a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift index 4365d1041..75aafd864 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift @@ -212,6 +212,7 @@ enum ActionsTarget: Equatable, Identifiable { private let messageReply: Binding? private let reportJunkActionsTarget: Binding? private let reportedForPhishingMessage: Binding? + private let reportedForDisplayProblemMessage: Binding? private let completionHandler: (() -> Void)? private let matomoCategory: MatomoUtils.EventCategory? diff --git a/Mail/Views/Thread/ThreadView.swift b/Mail/Views/Thread/ThreadView.swift index 4c8190f1b..0d96ea8e5 100644 --- a/Mail/Views/Thread/ThreadView.swift +++ b/Mail/Views/Thread/ThreadView.swift @@ -32,13 +32,6 @@ private struct ScrollOffsetPreferenceKey: PreferenceKey { } } -class MessageBottomSheet: DisplayedFloatingPanelState { - enum State: Equatable { - case contact(Recipient, isRemote: Bool) - case replyOption(Message, isThread: Bool) - } -} - struct ThreadView: View { @EnvironmentObject private var splitViewManager: SplitViewManager @EnvironmentObject private var mailboxManager: MailboxManager From e09105023ef80920070aa924b895cd4153340b8c Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Thu, 27 Apr 2023 17:11:45 +0200 Subject: [PATCH 13/30] refactor: Move mailNavigationPath to NavigationStore --- Mail/Utils/NavigationStore.swift | 1 + Mail/Views/SplitView.swift | 17 ++--------------- Mail/Views/Thread List/ThreadListCell.swift | 1 - Mail/Views/Thread List/ThreadListView.swift | 6 +++--- Mail/Views/Thread/ThreadView.swift | 3 +-- 5 files changed, 7 insertions(+), 21 deletions(-) diff --git a/Mail/Utils/NavigationStore.swift b/Mail/Utils/NavigationStore.swift index e3f28a914..9c34f259e 100644 --- a/Mail/Utils/NavigationStore.swift +++ b/Mail/Utils/NavigationStore.swift @@ -22,4 +22,5 @@ import MailCore class NavigationStore: ObservableObject { @Published var messageReply: MessageReply? + @Published var threadPath = [Thread]() } diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index 5c47cbdcb..42be5d451 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -25,17 +25,6 @@ import NavigationBackport import RealmSwift import SwiftUI -struct MailNavigationPathKey: EnvironmentKey { - static var defaultValue: Binding<[Thread]>? -} - -extension EnvironmentValues { - var mailNavigationPath: Binding<[Thread]>? { - get { self[MailNavigationPathKey.self] } - set { self[MailNavigationPathKey.self] = newValue } - } -} - public class SplitViewManager: ObservableObject { @Published var showSearch = false @Published var selectedFolder: Folder? @@ -57,7 +46,6 @@ struct SplitView: View { @Environment(\.window) var window @StateObject private var splitViewManager: SplitViewManager - @State private var path = [Thread]() var isCompact: Bool { UIConstants.isCompact(horizontalSizeClass: horizontalSizeClass, verticalSizeClass: verticalSizeClass) @@ -73,7 +61,7 @@ struct SplitView: View { Group { if isCompact { ZStack { - NBNavigationStack(path: $path) { + NBNavigationStack(path: $navigationStore.threadPath) { ThreadListManagerView(isCompact: isCompact) .accessibilityHidden(navigationDrawerController.isOpen) .nbNavigationDestination(for: Thread.self) { thread in @@ -94,7 +82,7 @@ struct SplitView: View { ThreadListManagerView(isCompact: isCompact) - if let thread = path.last { + if let thread = navigationStore.threadPath.last { ThreadView(thread: thread) } else { EmptyStateView.emptyThread(from: splitViewManager.selectedFolder) @@ -135,7 +123,6 @@ struct SplitView: View { setupBehaviour(orientation: interfaceOrientation) splitViewController.preferredDisplayMode = .twoDisplaceSecondary } - .environment(\.mailNavigationPath, $path) .environment(\.realmConfiguration, mailboxManager.realmConfiguration) .environmentObject(mailboxManager) .environmentObject(splitViewManager) diff --git a/Mail/Views/Thread List/ThreadListCell.swift b/Mail/Views/Thread List/ThreadListCell.swift index 45f3296d2..d0b559151 100644 --- a/Mail/Views/Thread List/ThreadListCell.swift +++ b/Mail/Views/Thread List/ThreadListCell.swift @@ -25,7 +25,6 @@ import SwiftUI struct ThreadListCell: View { @EnvironmentObject var splitViewManager: SplitViewManager - @Environment(\.mailNavigationPath) private var path let thread: Thread diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index 269cc7600..ec5f90c55 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -46,7 +46,7 @@ struct ThreadListView: View { @StateObject var multipleSelectionViewModel: ThreadListMultipleSelectionViewModel @EnvironmentObject var splitViewManager: SplitViewManager - @Environment(\.mailNavigationPath) private var path + @EnvironmentObject var navigationStore: NavigationStore @AppStorage(UserDefaults.shared.key(.threadDensity)) private var threadDensity = DefaultPreferences.threadDensity @AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor @@ -213,9 +213,9 @@ struct ThreadListView: View { } .onChange(of: viewModel.selectedThread) { newThread in if let newThread { - path?.wrappedValue = [newThread] + navigationStore.threadPath = [newThread] } else { - path?.wrappedValue = [] + navigationStore.threadPath = [] } } .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in diff --git a/Mail/Views/Thread/ThreadView.swift b/Mail/Views/Thread/ThreadView.swift index 0d96ea8e5..43b172a82 100644 --- a/Mail/Views/Thread/ThreadView.swift +++ b/Mail/Views/Thread/ThreadView.swift @@ -37,7 +37,6 @@ struct ThreadView: View { @EnvironmentObject private var mailboxManager: MailboxManager @EnvironmentObject private var navigationStore: NavigationStore - @Environment(\.mailNavigationPath) private var path @Environment(\.horizontalSizeClass) private var sizeClass @Environment(\.verticalSizeClass) var verticalSizeClass @Environment(\.dismiss) var dismiss @@ -130,7 +129,7 @@ struct ThreadView: View { if isCompact { dismiss() // For iPhone } else { - path?.wrappedValue = [] // For iPad + navigationStore.threadPath = [] // For iPad } } } From f5fafd77a8fa3d955ac9eefe77d71a8ac6815e69 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Fri, 28 Apr 2023 14:35:26 +0200 Subject: [PATCH 14/30] fix: Use StateObject instead of ObservedObject --- .../Bottom sheets/Actions/ActionsView.swift | 18 +++++++++--------- .../Actions/ReplyActionsView.swift | 10 +++++----- .../Bottom sheets/Actions/ReportJunkView.swift | 8 ++++---- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Mail/Views/Bottom sheets/Actions/ActionsView.swift b/Mail/Views/Bottom sheets/Actions/ActionsView.swift index 5f5971ef6..3259f9b09 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsView.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsView.swift @@ -23,7 +23,7 @@ import MailResources import SwiftUI struct ActionsView: View { - @ObservedObject var viewModel: ActionsViewModel + @StateObject var viewModel: ActionsViewModel init(mailboxManager: MailboxManager, target: ActionsTarget, @@ -37,14 +37,14 @@ struct ActionsView: View { matomoCategory = .bottomSheetThreadActions } - viewModel = ActionsViewModel(mailboxManager: mailboxManager, - target: target, - moveSheet: moveSheet, - messageReply: messageReply, - reportJunkActionsTarget: reportJunkActionsTarget, - reportedForDisplayProblemMessage: reportedForDisplayProblemMessage, - matomoCategory: matomoCategory, - completionHandler: completionHandler) + _viewModel = StateObject(wrappedValue: ActionsViewModel(mailboxManager: mailboxManager, + target: target, + moveSheet: moveSheet, + messageReply: messageReply, + reportJunkActionsTarget: reportJunkActionsTarget, + reportedForDisplayProblemMessage: reportedForDisplayProblemMessage, + matomoCategory: matomoCategory, + completionHandler: completionHandler)) } var body: some View { diff --git a/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift b/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift index 404f1a32e..46ecb8abf 100644 --- a/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift +++ b/Mail/Views/Bottom sheets/Actions/ReplyActionsView.swift @@ -22,17 +22,17 @@ import MailCore import SwiftUI struct ReplyActionsView: View { - @ObservedObject var viewModel: ActionsViewModel + @StateObject var viewModel: ActionsViewModel var quickActions: [Action] = [.reply, .replyAll] init(mailboxManager: MailboxManager, message: Message, messageReply: Binding?) { - viewModel = ActionsViewModel(mailboxManager: mailboxManager, - target: .message(message), - messageReply: messageReply, - matomoCategory: .replyBottomSheet) + _viewModel = StateObject(wrappedValue: ActionsViewModel(mailboxManager: mailboxManager, + target: .message(message), + messageReply: messageReply, + matomoCategory: .replyBottomSheet)) } var body: some View { diff --git a/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift b/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift index 5f5c7f053..f940fb408 100644 --- a/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift +++ b/Mail/Views/Bottom sheets/Actions/ReportJunkView.swift @@ -22,16 +22,16 @@ import MailCore import SwiftUI struct ReportJunkView: View { - @ObservedObject var viewModel: ActionsViewModel + @StateObject var viewModel: ActionsViewModel var actions: [Action] = [] init(mailboxManager: MailboxManager, target: ActionsTarget, reportedForPhishingMessage: Binding) { - viewModel = ActionsViewModel(mailboxManager: mailboxManager, - target: target, - reportedForPhishingMessage: reportedForPhishingMessage) + _viewModel = StateObject(wrappedValue: ActionsViewModel(mailboxManager: mailboxManager, + target: target, + reportedForPhishingMessage: reportedForPhishingMessage)) if case .message(let message) = target { let spam = message.folder?.role == .spam actions.append(contentsOf: [ From d63bee261844c67382b7fbc167084f66ba61226d Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Mon, 1 May 2023 16:49:29 +0200 Subject: [PATCH 15/30] fix: Use Dispatch reportDisplayProblem --- Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift index 75aafd864..f7e89bcb5 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift @@ -482,7 +482,9 @@ enum ActionsTarget: Equatable, Identifiable { private func reportDisplayProblem() { // This action is only available on a single message guard case .message(let message) = target else { return } - reportedForDisplayProblemMessage?.wrappedValue = message + DispatchQueue.main.async { [weak self] in + self?.reportedForDisplayProblemMessage?.wrappedValue = message + } } private func editMenu() { From 26d4251a52c2a9ae34731ec98fd5df16ceec4b64 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 2 May 2023 09:10:33 +0200 Subject: [PATCH 16/30] feat: Display popover instead of panel on large size --- Mail/Components/ActionsPanelButton.swift | 62 ++++++++++++++++ Mail/Components/ToolbarButton.swift | 29 ++++---- .../Actions/ActionsPopOverViewModifier.swift | 74 +++++++++++++++++++ Mail/Views/Thread List/ThreadListCell.swift | 2 - Mail/Views/Thread List/ThreadListView.swift | 7 +- .../Thread/MessageHeaderSummaryView.swift | 7 +- Mail/Views/Thread/MessageHeaderView.swift | 4 - Mail/Views/Thread/ThreadView.swift | 10 +-- 8 files changed, 161 insertions(+), 34 deletions(-) create mode 100644 Mail/Components/ActionsPanelButton.swift create mode 100644 Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift diff --git a/Mail/Components/ActionsPanelButton.swift b/Mail/Components/ActionsPanelButton.swift new file mode 100644 index 000000000..2e1b94627 --- /dev/null +++ b/Mail/Components/ActionsPanelButton.swift @@ -0,0 +1,62 @@ +/* + 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 CocoaLumberjackSwift +import MailCore +import MailResources +import SwiftUI + +struct ActionsPanelButton: View { + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @Environment(\.verticalSizeClass) private var verticalSizeClass + + @State private var actionsTarget: ActionsTarget? + + var message: Message? + var threads: [Thread]? + var isMultiSelectionEnabled = false + @ViewBuilder var label: () -> Content + + var isCompact: Bool { + horizontalSizeClass == .compact || verticalSizeClass == .compact + } + + var body: some View { + if isCompact { + buttonBody + .actionsPanel(actionsTarget: $actionsTarget) + } else { + buttonBody + .actionsPopOver(actionsTarget: $actionsTarget) + } + } + + var buttonBody: some View { + Button { + if let message { + actionsTarget = .message(message) + } else if let threads { + actionsTarget = .threads(threads, isMultiSelectionEnabled) + } else { + DDLogWarn("MoreButton has no action target, did you forget to set message or threads ?") + } + } label: { + label() + } + } +} diff --git a/Mail/Components/ToolbarButton.swift b/Mail/Components/ToolbarButton.swift index 31f9a345f..ac4810be2 100644 --- a/Mail/Components/ToolbarButton.swift +++ b/Mail/Components/ToolbarButton.swift @@ -20,28 +20,31 @@ import MailCore import MailResources import SwiftUI -struct ToolbarButton: View { +struct ToolbarButtonLabel: View { @Environment(\.verticalSizeClass) private var sizeClass let text: String let icon: Image - let action: () -> Void - init(text: String, icon: Image, action: @escaping () -> Void) { - self.text = text - self.icon = icon - self.action = action + var body: some View { + Label { + Text(text) + .textStyle(MailTextStyle.labelMediumAccent) + } icon: { + icon + } + .dynamicLabelStyle(sizeClass: sizeClass ?? .regular) } +} + +struct ToolbarButton: View { + let text: String + let icon: Image + let action: () -> Void var body: some View { Button(action: action) { - Label { - Text(text) - .textStyle(MailTextStyle.labelMediumAccent) - } icon: { - icon - } - .dynamicLabelStyle(sizeClass: sizeClass ?? .regular) + ToolbarButtonLabel(text: text, icon: icon) } .frame(maxWidth: .infinity) } diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift new file mode 100644 index 000000000..8200e5edb --- /dev/null +++ b/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift @@ -0,0 +1,74 @@ +/* + 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 MailCore +import SwiftUI + +extension View { + func actionsPopOver(actionsTarget: Binding, completionHandler: (() -> Void)? = nil) -> some View { + return modifier(ActionsPopOverViewModifier(actionsTarget: actionsTarget, completionHandler: completionHandler)) + } +} + +struct ActionsPopOverViewModifier: ViewModifier { + @EnvironmentObject private var mailboxManager: MailboxManager + @EnvironmentObject private var navigationStore: NavigationStore + + @StateObject private var moveSheet = MoveSheet() + + @Binding var actionsTarget: ActionsTarget? + @State var reportJunkActionsTarget: ActionsTarget? + @State var reportedForPhishingMessage: Message? + @State var reportedForDisplayProblemMessage: Message? + + var completionHandler: (() -> Void)? + + func body(content: Content) -> some View { + content + .popover(item: $actionsTarget) { target in + ScrollView { + ActionsView(mailboxManager: mailboxManager, + target: target, + moveSheet: moveSheet, + messageReply: $navigationStore.messageReply, + reportJunkActionsTarget: $reportJunkActionsTarget, + reportedForDisplayProblemMessage: $reportedForDisplayProblemMessage) { + completionHandler?() + } + .padding(.vertical) + } + } + .sheet(isPresented: $moveSheet.isShowing) { + if case .move(let folderId, let handler) = moveSheet.state { + MoveEmailView.sheetView(from: folderId, moveHandler: handler) + } + } + .floatingPanel(item: $reportJunkActionsTarget) { target in + ReportJunkView(mailboxManager: mailboxManager, + target: target, + reportedForPhishingMessage: $reportedForPhishingMessage) + } + .customAlert(item: $reportedForPhishingMessage) { message in + ReportPhishingView(message: message) + } + .customAlert(item: $reportedForDisplayProblemMessage) { message in + ReportDisplayProblemView(message: message) + } + } +} diff --git a/Mail/Views/Thread List/ThreadListCell.swift b/Mail/Views/Thread List/ThreadListCell.swift index d0b559151..4ab15729d 100644 --- a/Mail/Views/Thread List/ThreadListCell.swift +++ b/Mail/Views/Thread List/ThreadListCell.swift @@ -38,8 +38,6 @@ struct ThreadListCell: View { @Binding var editedMessageDraft: Draft? - @State private var shouldNavigateToThreadList = false - private var selectionType: SelectionBackgroundKind { if multipleSelectionViewModel.isEnabled { return isMultiSelected ? .multiple : .none diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index ec5f90c55..8d97e57b5 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -365,9 +365,10 @@ private struct ThreadListToolbar: ViewModifier { .disabled(action == .archive && splitViewManager.selectedFolder?.role == .archive) } - ToolbarButton(text: MailResourcesStrings.Localizable.buttonMore, - icon: MailResourcesAsset.plusActions.swiftUIImage) { - viewModel.actionsTarget = .threads(Array(multipleSelectionViewModel.selectedItems), true) + ActionsPanelButton(threads: Array(multipleSelectionViewModel.selectedItems), + isMultiSelectionEnabled: true) { + ToolbarButtonLabel(text: MailResourcesStrings.Localizable.buttonMore, + icon: MailResourcesAsset.plusActions.swiftUIImage) } } .disabled(multipleSelectionViewModel.selectedItems.isEmpty) diff --git a/Mail/Views/Thread/MessageHeaderSummaryView.swift b/Mail/Views/Thread/MessageHeaderSummaryView.swift index a4d0365de..25b7e608e 100644 --- a/Mail/Views/Thread/MessageHeaderSummaryView.swift +++ b/Mail/Views/Thread/MessageHeaderSummaryView.swift @@ -30,7 +30,6 @@ struct MessageHeaderSummaryView: View { @Binding var isHeaderExpanded: Bool let deleteDraftTapped: () -> Void let replyButtonTapped: () -> Void - let moreButtonTapped: () -> Void let recipientTapped: (Recipient) -> Void @LazyInjectService private var matomo: MatomoUtils @@ -107,7 +106,7 @@ struct MessageHeaderSummaryView: View { .scaledToFit() .frame(width: 20, height: 20) } - Button(action: moreButtonTapped) { + ActionsPanelButton(message: message) { MailResourcesAsset.plusActions.swiftUIImage .resizable() .scaledToFit() @@ -129,8 +128,6 @@ struct MessageHeaderSummaryView_Previews: PreviewProvider { // Preview } replyButtonTapped: { // Preview - } moreButtonTapped: { - // Preview } recipientTapped: { _ in // Preview } @@ -140,8 +137,6 @@ struct MessageHeaderSummaryView_Previews: PreviewProvider { // Preview } replyButtonTapped: { // Preview - } moreButtonTapped: { - // Preview } recipientTapped: { _ in // Preview } diff --git a/Mail/Views/Thread/MessageHeaderView.swift b/Mail/Views/Thread/MessageHeaderView.swift index 8113fe859..db0ab4312 100644 --- a/Mail/Views/Thread/MessageHeaderView.swift +++ b/Mail/Views/Thread/MessageHeaderView.swift @@ -30,7 +30,6 @@ struct MessageHeaderView: View { @State private var editedDraft: Draft? @State private var contactViewRecipient: Recipient? @State private var replyOrReplyAllMessage: Message? - @State private var actionsTarget: ActionsTarget? @ObservedRealmObject var message: Message @Binding var isHeaderExpanded: Bool @@ -52,8 +51,6 @@ struct MessageHeaderView: View { } else { navigationStore.messageReply = MessageReply(message: message, replyMode: .reply) } - } moreButtonTapped: { - actionsTarget = .message(message) } recipientTapped: { recipient in contactViewRecipient = recipient } @@ -82,7 +79,6 @@ struct MessageHeaderView: View { .floatingPanel(item: $contactViewRecipient) { recipient in ContactActionsView(recipient: recipient) } - .actionsPanel(actionsTarget: $actionsTarget) .floatingPanel(item: $replyOrReplyAllMessage) { message in ReplyActionsView(mailboxManager: mailboxManager, message: message, messageReply: $navigationStore.messageReply) } diff --git a/Mail/Views/Thread/ThreadView.swift b/Mail/Views/Thread/ThreadView.swift index 43b172a82..8a7c21ba7 100644 --- a/Mail/Views/Thread/ThreadView.swift +++ b/Mail/Views/Thread/ThreadView.swift @@ -38,7 +38,7 @@ struct ThreadView: View { @EnvironmentObject private var navigationStore: NavigationStore @Environment(\.horizontalSizeClass) private var sizeClass - @Environment(\.verticalSizeClass) var verticalSizeClass + @Environment(\.verticalSizeClass) private var verticalSizeClass @Environment(\.dismiss) var dismiss @ObservedRealmObject var thread: Thread @@ -46,7 +46,6 @@ struct ThreadView: View { @State private var headerHeight: CGFloat = 0 @State private var displayNavigationTitle = false @State private var replyOrReplyAllMessage: Message? - @State private var actionsTarget: ActionsTarget? var isCompact: Bool { sizeClass == .compact || verticalSizeClass == .compact @@ -114,13 +113,12 @@ struct ThreadView: View { .disabled(action == .archive && thread.folder?.role == .archive) Spacer() } - ToolbarButton(text: MailResourcesStrings.Localizable.buttonMore, - icon: MailResourcesAsset.plusActions.swiftUIImage) { - actionsTarget = .threads([thread], false) + ActionsPanelButton(threads: [thread]) { + ToolbarButtonLabel(text: MailResourcesStrings.Localizable.buttonMore, + icon: MailResourcesAsset.plusActions.swiftUIImage) } } } - .actionsPanel(actionsTarget: $actionsTarget) .floatingPanel(item: $replyOrReplyAllMessage) { message in ReplyActionsView(mailboxManager: mailboxManager, message: message, messageReply: $navigationStore.messageReply) } From 12a3562d90855464bd6188820f5d98c9cc1d0392 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 2 May 2023 09:40:03 +0200 Subject: [PATCH 17/30] feat: AdaptivePanelViewModifier for message reply --- Mail/Utils/AdaptivePanelViewModifier.swift | 51 +++++++++++++++++++ .../Thread/MessageHeaderSummaryView.swift | 24 ++++++--- Mail/Views/Thread/MessageHeaderView.swift | 15 +----- Mail/Views/Thread/ThreadView.swift | 18 ++++--- 4 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 Mail/Utils/AdaptivePanelViewModifier.swift diff --git a/Mail/Utils/AdaptivePanelViewModifier.swift b/Mail/Utils/AdaptivePanelViewModifier.swift new file mode 100644 index 000000000..b441de5f0 --- /dev/null +++ b/Mail/Utils/AdaptivePanelViewModifier.swift @@ -0,0 +1,51 @@ +/* + 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 SwiftUI + +extension View { + func adaptivePanel(item: Binding, + @ViewBuilder content: @escaping (Item) -> Content) -> some View { + return modifier(AdaptivePanelViewModifier(item: item, panelContent: content)) + } +} + +struct AdaptivePanelViewModifier: ViewModifier { + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @Environment(\.verticalSizeClass) private var verticalSizeClass + + private var isCompact: Bool { + horizontalSizeClass == .compact || verticalSizeClass == .compact + } + + @Binding var item: Item? + @ViewBuilder var panelContent: (Item) -> PanelContent + + func body(content: Content) -> some View { + if isCompact { + content.floatingPanel(item: $item) { item in + panelContent(item) + } + } else { + content.popover(item: $item) { item in + panelContent(item) + .padding() + } + } + } +} diff --git a/Mail/Views/Thread/MessageHeaderSummaryView.swift b/Mail/Views/Thread/MessageHeaderSummaryView.swift index 25b7e608e..9e9e97f27 100644 --- a/Mail/Views/Thread/MessageHeaderSummaryView.swift +++ b/Mail/Views/Thread/MessageHeaderSummaryView.swift @@ -25,11 +25,14 @@ import RealmSwift import SwiftUI struct MessageHeaderSummaryView: View { + @EnvironmentObject private var mailboxManager: MailboxManager + @EnvironmentObject private var navigationStore: NavigationStore + @ObservedRealmObject var message: Message + @State private var replyOrReplyAllMessage: Message? @Binding var isMessageExpanded: Bool @Binding var isHeaderExpanded: Bool let deleteDraftTapped: () -> Void - let replyButtonTapped: () -> Void let recipientTapped: (Recipient) -> Void @LazyInjectService private var matomo: MatomoUtils @@ -100,12 +103,25 @@ struct MessageHeaderSummaryView: View { if isMessageExpanded { HStack(spacing: 20) { - Button(action: replyButtonTapped) { + Button { + matomo.track(eventWithCategory: .messageActions, name: "reply") + if message.canReplyAll { + replyOrReplyAllMessage = message + } else { + navigationStore.messageReply = MessageReply(message: message, replyMode: .reply) + } + + } label: { MailResourcesAsset.emailActionReply.swiftUIImage .resizable() .scaledToFit() .frame(width: 20, height: 20) } + .adaptivePanel(item: $replyOrReplyAllMessage) { message in + ReplyActionsView(mailboxManager: mailboxManager, + message: message, + messageReply: $navigationStore.messageReply) + } ActionsPanelButton(message: message) { MailResourcesAsset.plusActions.swiftUIImage .resizable() @@ -126,8 +142,6 @@ struct MessageHeaderSummaryView_Previews: PreviewProvider { isMessageExpanded: .constant(false), isHeaderExpanded: .constant(false)) { // Preview - } replyButtonTapped: { - // Preview } recipientTapped: { _ in // Preview } @@ -135,8 +149,6 @@ struct MessageHeaderSummaryView_Previews: PreviewProvider { isMessageExpanded: .constant(true), isHeaderExpanded: .constant(false)) { // Preview - } replyButtonTapped: { - // Preview } recipientTapped: { _ in // Preview } diff --git a/Mail/Views/Thread/MessageHeaderView.swift b/Mail/Views/Thread/MessageHeaderView.swift index db0ab4312..55ae29435 100644 --- a/Mail/Views/Thread/MessageHeaderView.swift +++ b/Mail/Views/Thread/MessageHeaderView.swift @@ -26,6 +26,7 @@ import SwiftUI struct MessageHeaderView: View { @EnvironmentObject private var navigationStore: NavigationStore + @EnvironmentObject private var mailboxManager: MailboxManager @State private var editedDraft: Draft? @State private var contactViewRecipient: Recipient? @@ -35,8 +36,6 @@ struct MessageHeaderView: View { @Binding var isHeaderExpanded: Bool @Binding var isMessageExpanded: Bool - @EnvironmentObject var mailboxManager: MailboxManager - @LazyInjectService private var matomo: MatomoUtils var body: some View { @@ -44,14 +43,7 @@ struct MessageHeaderView: View { MessageHeaderSummaryView(message: message, isMessageExpanded: $isMessageExpanded, isHeaderExpanded: $isHeaderExpanded, - deleteDraftTapped: deleteDraft) { - matomo.track(eventWithCategory: .messageActions, name: "reply") - if message.canReplyAll { - replyOrReplyAllMessage = message - } else { - navigationStore.messageReply = MessageReply(message: message, replyMode: .reply) - } - } recipientTapped: { recipient in + deleteDraftTapped: deleteDraft) { recipient in contactViewRecipient = recipient } @@ -79,9 +71,6 @@ struct MessageHeaderView: View { .floatingPanel(item: $contactViewRecipient) { recipient in ContactActionsView(recipient: recipient) } - .floatingPanel(item: $replyOrReplyAllMessage) { message in - ReplyActionsView(mailboxManager: mailboxManager, message: message, messageReply: $navigationStore.messageReply) - } } private func deleteDraft() { diff --git a/Mail/Views/Thread/ThreadView.swift b/Mail/Views/Thread/ThreadView.swift index 8a7c21ba7..c2bb50d0a 100644 --- a/Mail/Views/Thread/ThreadView.swift +++ b/Mail/Views/Thread/ThreadView.swift @@ -107,10 +107,19 @@ struct ThreadView: View { } ToolbarItemGroup(placement: .bottomBar) { ForEach(toolbarActions) { action in - ToolbarButton(text: action.title, icon: action.icon) { - didTap(action: action) + if action == .reply { + ToolbarButton(text: action.title, icon: action.icon) { + didTap(action: action) + } + .adaptivePanel(item: $replyOrReplyAllMessage) { message in + ReplyActionsView(mailboxManager: mailboxManager, message: message, messageReply: $navigationStore.messageReply) + } + } else { + ToolbarButton(text: action.title, icon: action.icon) { + didTap(action: action) + } + .disabled(action == .archive && thread.folder?.role == .archive) } - .disabled(action == .archive && thread.folder?.role == .archive) Spacer() } ActionsPanelButton(threads: [thread]) { @@ -119,9 +128,6 @@ struct ThreadView: View { } } } - .floatingPanel(item: $replyOrReplyAllMessage) { message in - ReplyActionsView(mailboxManager: mailboxManager, message: message, messageReply: $navigationStore.messageReply) - } .onChange(of: thread.messages) { newMessagesList in if newMessagesList.isEmpty || thread.messageInFolderCount == 0 { if isCompact { From 0fe8380694ee2e887e960653633df60abca2d30d Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 2 May 2023 09:52:55 +0200 Subject: [PATCH 18/30] feat: AdaptivePanelViewModifier for contacts --- .../Thread/MessageHeaderDetailView.swift | 24 +++++++++---------- .../Thread/MessageHeaderSummaryView.swift | 14 ++++++----- Mail/Views/Thread/MessageHeaderView.swift | 13 ++-------- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/Mail/Views/Thread/MessageHeaderDetailView.swift b/Mail/Views/Thread/MessageHeaderDetailView.swift index 0f4606c87..58a26abdd 100644 --- a/Mail/Views/Thread/MessageHeaderDetailView.swift +++ b/Mail/Views/Thread/MessageHeaderDetailView.swift @@ -44,7 +44,6 @@ struct ViewGeometry: View { struct MessageHeaderDetailView: View { @ObservedRealmObject var message: Message - let recipientTapped: (Recipient) -> Void @State private var labelWidth: CGFloat = 100 @@ -53,29 +52,25 @@ struct MessageHeaderDetailView: View { RecipientLabel( labelWidth: $labelWidth, title: MailResourcesStrings.Localizable.fromTitle, - recipients: message.from, - recipientTapped: recipientTapped + recipients: message.from ) RecipientLabel( labelWidth: $labelWidth, title: MailResourcesStrings.Localizable.toTitle, - recipients: message.to, - recipientTapped: recipientTapped + recipients: message.to ) if !message.cc.isEmpty { RecipientLabel( labelWidth: $labelWidth, title: MailResourcesStrings.Localizable.ccTitle, - recipients: message.cc, - recipientTapped: recipientTapped + recipients: message.cc ) } if !message.bcc.isEmpty { RecipientLabel( labelWidth: $labelWidth, title: MailResourcesStrings.Localizable.bccTitle, - recipients: message.bcc, - recipientTapped: recipientTapped + recipients: message.bcc ) } HStack { @@ -97,7 +92,7 @@ struct MessageHeaderDetailView: View { struct MessageHeaderDetailView_Previews: PreviewProvider { static var previews: some View { - MessageHeaderDetailView(message: PreviewHelper.sampleMessage) { _ in /* Preview */ } + MessageHeaderDetailView(message: PreviewHelper.sampleMessage) } } @@ -105,7 +100,8 @@ struct RecipientLabel: View { @Binding var labelWidth: CGFloat let title: String let recipients: RealmSwift.List - let recipientTapped: (Recipient) -> Void + + @State private var contactViewRecipient: Recipient? @LazyInjectService private var matomo: MatomoUtils @@ -120,13 +116,17 @@ struct RecipientLabel: View { WrappingHStack(lineSpacing: 2) { Button { matomo.track(eventWithCategory: .message, name: "selectRecipient") - recipientTapped(recipient) + contactViewRecipient = recipient } label: { Text(recipient.name.isEmpty ? recipient.email : recipient.name) .textStyle(.bodySmallAccent) .lineLimit(1) .layoutPriority(1) } + .adaptivePanel(item: $contactViewRecipient) { recipient in + ContactActionsView(recipient: recipient) + } + if !recipient.name.isEmpty { Text(recipient.email) .textStyle(.labelSecondary) diff --git a/Mail/Views/Thread/MessageHeaderSummaryView.swift b/Mail/Views/Thread/MessageHeaderSummaryView.swift index 9e9e97f27..b652537d1 100644 --- a/Mail/Views/Thread/MessageHeaderSummaryView.swift +++ b/Mail/Views/Thread/MessageHeaderSummaryView.swift @@ -29,11 +29,14 @@ struct MessageHeaderSummaryView: View { @EnvironmentObject private var navigationStore: NavigationStore @ObservedRealmObject var message: Message + @State private var replyOrReplyAllMessage: Message? + @State private var contactViewRecipient: Recipient? + @Binding var isMessageExpanded: Bool @Binding var isHeaderExpanded: Bool + let deleteDraftTapped: () -> Void - let recipientTapped: (Recipient) -> Void @LazyInjectService private var matomo: MatomoUtils @@ -43,10 +46,13 @@ struct MessageHeaderSummaryView: View { if let recipient = message.from.first { Button { matomo.track(eventWithCategory: .message, name: "selectAvatar") - recipientTapped(recipient) + contactViewRecipient = recipient } label: { AvatarView(avatarDisplayable: recipient, size: 40) } + .adaptivePanel(item: $contactViewRecipient) { recipient in + ContactActionsView(recipient: recipient) + } } VStack(alignment: .leading, spacing: 4) { @@ -142,15 +148,11 @@ struct MessageHeaderSummaryView_Previews: PreviewProvider { isMessageExpanded: .constant(false), isHeaderExpanded: .constant(false)) { // Preview - } recipientTapped: { _ in - // Preview } MessageHeaderSummaryView(message: PreviewHelper.sampleMessage, isMessageExpanded: .constant(true), isHeaderExpanded: .constant(false)) { // Preview - } recipientTapped: { _ in - // Preview } } .previewLayout(.sizeThatFits) diff --git a/Mail/Views/Thread/MessageHeaderView.swift b/Mail/Views/Thread/MessageHeaderView.swift index 55ae29435..33caa32fc 100644 --- a/Mail/Views/Thread/MessageHeaderView.swift +++ b/Mail/Views/Thread/MessageHeaderView.swift @@ -29,8 +29,6 @@ struct MessageHeaderView: View { @EnvironmentObject private var mailboxManager: MailboxManager @State private var editedDraft: Draft? - @State private var contactViewRecipient: Recipient? - @State private var replyOrReplyAllMessage: Message? @ObservedRealmObject var message: Message @Binding var isHeaderExpanded: Bool @@ -43,14 +41,10 @@ struct MessageHeaderView: View { MessageHeaderSummaryView(message: message, isMessageExpanded: $isMessageExpanded, isHeaderExpanded: $isHeaderExpanded, - deleteDraftTapped: deleteDraft) { recipient in - contactViewRecipient = recipient - } + deleteDraftTapped: deleteDraft) if isHeaderExpanded { - MessageHeaderDetailView(message: message) { recipient in - contactViewRecipient = recipient - } + MessageHeaderDetailView(message: message) } } .contentShape(Rectangle()) @@ -68,9 +62,6 @@ struct MessageHeaderView: View { .sheet(item: $editedDraft) { editedDraft in ComposeMessageView.editDraft(draft: editedDraft, mailboxManager: mailboxManager) } - .floatingPanel(item: $contactViewRecipient) { recipient in - ContactActionsView(recipient: recipient) - } } private func deleteDraft() { From 1e74fa4c4412e961516dbb565a65f268f5c5766c Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 2 May 2023 11:19:49 +0200 Subject: [PATCH 19/30] feat: Add isCompactWindow to present popover or panel accordingly --- Mail/Components/ActionsPanelButton.swift | 9 ++------- Mail/Utils/AdaptivePanelViewModifier.swift | 10 +++------- .../Utils/Environment+Extension.swift | 11 +++++++++++ .../Actions/ActionsPopOverViewModifier.swift | 1 + Mail/Views/SplitView.swift | 14 ++++++++------ 5 files changed, 25 insertions(+), 20 deletions(-) rename {MailCore => Mail}/Utils/Environment+Extension.swift (77%) diff --git a/Mail/Components/ActionsPanelButton.swift b/Mail/Components/ActionsPanelButton.swift index 2e1b94627..5f557c0b1 100644 --- a/Mail/Components/ActionsPanelButton.swift +++ b/Mail/Components/ActionsPanelButton.swift @@ -22,8 +22,7 @@ import MailResources import SwiftUI struct ActionsPanelButton: View { - @Environment(\.horizontalSizeClass) private var horizontalSizeClass - @Environment(\.verticalSizeClass) private var verticalSizeClass + @Environment(\.isCompactWindow) private var isCompactWindow @State private var actionsTarget: ActionsTarget? @@ -32,12 +31,8 @@ struct ActionsPanelButton: View { var isMultiSelectionEnabled = false @ViewBuilder var label: () -> Content - var isCompact: Bool { - horizontalSizeClass == .compact || verticalSizeClass == .compact - } - var body: some View { - if isCompact { + if isCompactWindow { buttonBody .actionsPanel(actionsTarget: $actionsTarget) } else { diff --git a/Mail/Utils/AdaptivePanelViewModifier.swift b/Mail/Utils/AdaptivePanelViewModifier.swift index b441de5f0..170e50e0a 100644 --- a/Mail/Utils/AdaptivePanelViewModifier.swift +++ b/Mail/Utils/AdaptivePanelViewModifier.swift @@ -26,18 +26,13 @@ extension View { } struct AdaptivePanelViewModifier: ViewModifier { - @Environment(\.horizontalSizeClass) private var horizontalSizeClass - @Environment(\.verticalSizeClass) private var verticalSizeClass - - private var isCompact: Bool { - horizontalSizeClass == .compact || verticalSizeClass == .compact - } + @Environment(\.isCompactWindow) private var isCompactWindow @Binding var item: Item? @ViewBuilder var panelContent: (Item) -> PanelContent func body(content: Content) -> some View { - if isCompact { + if isCompactWindow { content.floatingPanel(item: $item) { item in panelContent(item) } @@ -45,6 +40,7 @@ struct AdaptivePanelViewModifier: ViewMo content.popover(item: $item) { item in panelContent(item) .padding() + .frame(idealWidth: 400) } } } diff --git a/MailCore/Utils/Environment+Extension.swift b/Mail/Utils/Environment+Extension.swift similarity index 77% rename from MailCore/Utils/Environment+Extension.swift rename to Mail/Utils/Environment+Extension.swift index fab8c86fc..cd1468be5 100644 --- a/MailCore/Utils/Environment+Extension.swift +++ b/Mail/Utils/Environment+Extension.swift @@ -29,3 +29,14 @@ public extension EnvironmentValues { set { self[WindowKey.self] = newValue } } } + +public struct CompactWindowKey: EnvironmentKey { + public static let defaultValue = true +} + +public extension EnvironmentValues { + var isCompactWindow: CompactWindowKey.Value { + get { return self[CompactWindowKey.self] } + set { self[CompactWindowKey.self] = newValue } + } +} diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift index 8200e5edb..ce1c30ad3 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift @@ -53,6 +53,7 @@ struct ActionsPopOverViewModifier: ViewModifier { } .padding(.vertical) } + .frame(idealWidth: 400) } .sheet(isPresented: $moveSheet.isShowing) { if case .move(let folderId, let handler) = moveSheet.state { diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index 42be5d451..e1daace86 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -36,18 +36,19 @@ public class SplitViewManager: ObservableObject { } struct SplitView: View { - var mailboxManager: MailboxManager - @State var splitViewController: UISplitViewController? - @StateObject private var navigationDrawerController = NavigationDrawerState() - @StateObject private var navigationStore = NavigationStore() - @Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.verticalSizeClass) var verticalSizeClass @Environment(\.window) var window + @State var splitViewController: UISplitViewController? + + @StateObject private var navigationDrawerController = NavigationDrawerState() + @StateObject private var navigationStore = NavigationStore() @StateObject private var splitViewManager: SplitViewManager - var isCompact: Bool { + let mailboxManager: MailboxManager + + private var isCompact: Bool { UIConstants.isCompact(horizontalSizeClass: horizontalSizeClass, verticalSizeClass: verticalSizeClass) } @@ -124,6 +125,7 @@ struct SplitView: View { splitViewController.preferredDisplayMode = .twoDisplaceSecondary } .environment(\.realmConfiguration, mailboxManager.realmConfiguration) + .environment(\.isCompactWindow, horizontalSizeClass == .compact || verticalSizeClass == .compact) .environmentObject(mailboxManager) .environmentObject(splitViewManager) .environmentObject(navigationDrawerController) From ef7dfe9bbcb8b0c4a46d6d044dac56e5a9d2596d Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 2 May 2023 14:03:38 +0200 Subject: [PATCH 20/30] feat: AdaptiveActionsViewModifier for swipe actions --- Mail/Utils/AdaptivePanelViewModifier.swift | 20 +++ Mail/Views/Thread List/ThreadListCell.swift | 5 +- .../Thread List/ThreadListSwipeAction.swift | 115 ++++++++++++++---- Mail/Views/Thread List/ThreadListView.swift | 11 -- .../Thread List/ThreadListViewModel.swift | 56 --------- 5 files changed, 112 insertions(+), 95 deletions(-) diff --git a/Mail/Utils/AdaptivePanelViewModifier.swift b/Mail/Utils/AdaptivePanelViewModifier.swift index 170e50e0a..55b77a8e6 100644 --- a/Mail/Utils/AdaptivePanelViewModifier.swift +++ b/Mail/Utils/AdaptivePanelViewModifier.swift @@ -45,3 +45,23 @@ struct AdaptivePanelViewModifier: ViewMo } } } + +extension View { + func adaptiveActions(item: Binding) -> some View { + return modifier(AdaptiveActionsViewModifier(item: item)) + } +} + +struct AdaptiveActionsViewModifier: ViewModifier { + @Environment(\.isCompactWindow) private var isCompactWindow + + @Binding var item: ActionsTarget? + + func body(content: Content) -> some View { + if isCompactWindow { + content.actionsPanel(actionsTarget: $item) + } else { + content.actionsPopOver(actionsTarget: $item) + } + } +} diff --git a/Mail/Views/Thread List/ThreadListCell.swift b/Mail/Views/Thread List/ThreadListCell.swift index 4ab15729d..97201fd5b 100644 --- a/Mail/Views/Thread List/ThreadListCell.swift +++ b/Mail/Views/Thread List/ThreadListCell.swift @@ -28,8 +28,8 @@ struct ThreadListCell: View { let thread: Thread - @ObservedObject var viewModel: ThreadListViewModel - @ObservedObject var multipleSelectionViewModel: ThreadListMultipleSelectionViewModel + let viewModel: ThreadListViewModel + let multipleSelectionViewModel: ThreadListMultipleSelectionViewModel let threadDensity: ThreadDensity @@ -103,7 +103,6 @@ struct ThreadListCell_Previews: PreviewProvider { thread: PreviewHelper.sampleThread, viewModel: ThreadListViewModel(mailboxManager: PreviewHelper.sampleMailboxManager, folder: PreviewHelper.sampleFolder, - moveSheet: MoveSheet(), isCompact: false), multipleSelectionViewModel: ThreadListMultipleSelectionViewModel(mailboxManager: PreviewHelper.sampleMailboxManager), threadDensity: .large, diff --git a/Mail/Views/Thread List/ThreadListSwipeAction.swift b/Mail/Views/Thread List/ThreadListSwipeAction.swift index a91a898b9..2d42d57f7 100644 --- a/Mail/Views/Thread List/ThreadListSwipeAction.swift +++ b/Mail/Views/Thread List/ThreadListSwipeAction.swift @@ -24,24 +24,24 @@ import MailResources import SwiftUI private struct SwipeActionView: View { - private let thread: Thread - private let viewModel: ThreadListViewModel - private let action: SwipeAction - @LazyInjectService private var matomo: MatomoUtils - init(thread: Thread, viewModel: ThreadListViewModel, action: SwipeAction) { - self.thread = thread - self.viewModel = viewModel - self.action = action.fallback(for: thread) ?? action - } + @EnvironmentObject private var mailboxManager: MailboxManager + + @ObservedObject var moveSheet: MoveSheet + + @Binding var actionsTarget: ActionsTarget? + + let thread: Thread + let viewModel: ThreadListViewModel + let action: SwipeAction var body: some View { Button(role: action.isDestructive ? .destructive : nil) { matomo.track(eventWithCategory: .swipeActions, name: action.matomoName) Task { await tryOrDisplayError { - try await viewModel.handleSwipeAction(action, thread: thread) + try await handleSwipeAction(action, thread: thread) } } } label: { @@ -50,19 +50,72 @@ private struct SwipeActionView: View { } .tint(action.swipeTint) } + + func handleSwipeAction(_ action: SwipeAction, thread: Thread) async throws { + switch action { + case .delete: + try await mailboxManager.moveOrDelete(threads: [thread]) + case .archive: + try await move(thread: thread, to: .archive) + case .readUnread: + try await mailboxManager.toggleRead(threads: [thread]) + case .move: + moveSheet.state = .move(folderId: viewModel.folder.id) { folder in + guard thread.folder != folder else { return } + Task { + try await self.move(thread: thread, to: folder) + } + } + case .favorite: + try await mailboxManager.toggleStar(threads: [thread]) + case .postPone: + // TODO: Report action + showWorkInProgressSnackBar() + case .spam: + try await toggleSpam(thread: thread) + case .quickAction: + actionsTarget = .threads([thread.thaw() ?? thread], false) + case .none: + break + case .moveToInbox: + try await move(thread: thread, to: .inbox) + } + } + + private func toggleSpam(thread: Thread) async throws { + let destination: FolderRole = viewModel.folder.role == .spam ? .inbox : .spam + try await move(thread: thread, to: destination) + } + + private func move(thread: Thread, to folderRole: FolderRole) async throws { + guard let folder = mailboxManager.getFolder(with: folderRole)?.freeze() else { return } + try await move(thread: thread, to: folder) + } + + private func move(thread: Thread, to folder: Folder) async throws { + let response = try await mailboxManager.move(threads: [thread], to: folder) + IKSnackBar.showCancelableSnackBar(message: MailResourcesStrings.Localizable.snackbarThreadMoved(folder.localizedName), + cancelSuccessMessage: MailResourcesStrings.Localizable.snackbarMoveCancelled, + undoRedoAction: response, + mailboxManager: mailboxManager) + } } struct ThreadListSwipeActions: ViewModifier { - let thread: Thread - let viewModel: ThreadListViewModel - let multipleSelectionViewModel: ThreadListMultipleSelectionViewModel - @AppStorage(UserDefaults.shared.key(.swipeFullLeading)) private var swipeFullLeading = DefaultPreferences.swipeFullLeading @AppStorage(UserDefaults.shared.key(.swipeLeading)) private var swipeLeading = DefaultPreferences.swipeLeading @AppStorage(UserDefaults.shared.key(.swipeFullTrailing)) private var swipeFullTrailing = DefaultPreferences.swipeFullTrailing @AppStorage(UserDefaults.shared.key(.swipeTrailing)) private var swipeTrailing = DefaultPreferences.swipeTrailing + @StateObject private var moveSheet = MoveSheet() + + @State private var actionsTarget: ActionsTarget? + + let thread: Thread + let viewModel: ThreadListViewModel + let multipleSelectionViewModel: ThreadListMultipleSelectionViewModel + func body(content: Content) -> some View { if viewModel.folder.role == .draft { content @@ -77,6 +130,12 @@ struct ThreadListSwipeActions: ViewModifier { .swipeActions(edge: .trailing) { edgeActions([swipeFullTrailing, swipeTrailing]) } + .adaptiveActions(item: $actionsTarget) + .sheet(isPresented: $moveSheet.isShowing) { + if case .move(let folderId, let handler) = moveSheet.state { + MoveEmailView.sheetView(from: folderId, moveHandler: handler) + } + } } } @@ -84,7 +143,11 @@ struct ThreadListSwipeActions: ViewModifier { private func edgeActions(_ actions: [SwipeAction]) -> some View { if !multipleSelectionViewModel.isEnabled { ForEach(actions.filter { $0 != .none }, id: \.rawValue) { action in - SwipeActionView(thread: thread, viewModel: viewModel, action: action) + SwipeActionView(moveSheet: moveSheet, + actionsTarget: $actionsTarget, + thread: thread, + viewModel: viewModel, + action: action) } } } @@ -100,13 +163,15 @@ extension View { } } -struct ThreadListSwipeAction_Previews: PreviewProvider { - static var previews: some View { - SwipeActionView(thread: PreviewHelper.sampleThread, - viewModel: ThreadListViewModel(mailboxManager: PreviewHelper.sampleMailboxManager, - folder: PreviewHelper.sampleFolder, - moveSheet: MoveSheet(), - isCompact: false), - action: .delete) - } -} +/* + struct ThreadListSwipeAction_Previews: PreviewProvider { + static var previews: some View { + SwipeActionView(thread: PreviewHelper.sampleThread, + viewModel: ThreadListViewModel(mailboxManager: PreviewHelper.sampleMailboxManager, + folder: PreviewHelper.sampleFolder, + moveSheet: MoveSheet(), + isCompact: false), + action: .delete) + } + } + */ diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index 8d97e57b5..8b0db443d 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -52,7 +52,6 @@ struct ThreadListView: View { @AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor @State private var isShowingComposeNewMessageView = false - @StateObject var moveSheet: MoveSheet @StateObject private var networkMonitor = NetworkMonitor() @Binding private var editedMessageDraft: Draft? @Binding private var messageReply: MessageReply? @@ -81,10 +80,8 @@ struct ThreadListView: View { let moveEmailSheet = MoveSheet() _editedMessageDraft = editedMessageDraft _messageReply = messageReply - _moveSheet = StateObject(wrappedValue: moveEmailSheet) _viewModel = StateObject(wrappedValue: ThreadListViewModel(mailboxManager: mailboxManager, folder: folder, - moveSheet: moveEmailSheet, isCompact: isCompact)) _multipleSelectionViewModel = StateObject(wrappedValue: ThreadListMultipleSelectionViewModel(mailboxManager: mailboxManager)) @@ -201,9 +198,6 @@ struct ThreadListView: View { matomo.track(eventWithCategory: .newMessage, name: "openFromFab") isShowingComposeNewMessageView.toggle() } - .actionsPanel(actionsTarget: $viewModel.actionsTarget) { - multipleSelectionViewModel.isEnabled = false - } .onAppear { networkMonitor.start() viewModel.selectedThread = nil @@ -230,11 +224,6 @@ struct ThreadListView: View { .sheet(isPresented: $isShowingComposeNewMessageView) { ComposeMessageView.newMessage(mailboxManager: viewModel.mailboxManager) } - .sheet(isPresented: $moveSheet.isShowing) { - if case .move(let folderId, let handler) = moveSheet.state { - MoveEmailView.sheetView(from: folderId, moveHandler: handler) - } - } .customAlert(item: $flushAlert) { item in FlushFolderAlertView(flushAlert: item, folder: viewModel.folder) } diff --git a/Mail/Views/Thread List/ThreadListViewModel.swift b/Mail/Views/Thread List/ThreadListViewModel.swift index 01ad628e0..85f170636 100644 --- a/Mail/Views/Thread List/ThreadListViewModel.swift +++ b/Mail/Views/Thread List/ThreadListViewModel.swift @@ -99,7 +99,6 @@ class DateSection: Identifiable { @Published var isLoadingPage = false @Published var lastUpdate: Date? - @Published var actionsTarget: ActionsTarget? // Used to know thread location private var selectedThreadIndex: Int? @@ -111,8 +110,6 @@ class DateSection: Identifiable { } } - let moveSheet: MoveSheet - var scrollViewProxy: ScrollViewProxy? var isCompact: Bool @@ -147,13 +144,11 @@ class DateSection: Identifiable { init( mailboxManager: MailboxManager, folder: Folder, - moveSheet: MoveSheet, isCompact: Bool ) { self.mailboxManager = mailboxManager self.folder = folder lastUpdate = folder.lastUpdate - self.moveSheet = moveSheet self.isCompact = isCompact observeChanges() } @@ -284,55 +279,4 @@ class DateSection: Identifiable { return newSections } } - - // MARK: - Swipe actions - - func handleSwipeAction(_ action: SwipeAction, thread: Thread) async throws { - switch action { - case .delete: - try await mailboxManager.moveOrDelete(threads: [thread]) - case .archive: - try await move(thread: thread, to: .archive) - case .readUnread: - try await mailboxManager.toggleRead(threads: [thread]) - case .move: - moveSheet.state = .move(folderId: folder.id) { folder in - guard thread.folder != folder else { return } - Task { - try await self.move(thread: thread, to: folder) - } - } - case .favorite: - try await mailboxManager.toggleStar(threads: [thread]) - case .postPone: - // TODO: Report action - showWorkInProgressSnackBar() - case .spam: - try await toggleSpam(thread: thread) - case .quickAction: - actionsTarget = .threads([thread.thaw() ?? thread], false) - case .none: - break - case .moveToInbox: - try await move(thread: thread, to: .inbox) - } - } - - private func toggleSpam(thread: Thread) async throws { - let destination: FolderRole = folder.role == .spam ? .inbox : .spam - try await move(thread: thread, to: destination) - } - - private func move(thread: Thread, to folderRole: FolderRole) async throws { - guard let folder = mailboxManager.getFolder(with: folderRole)?.freeze() else { return } - try await move(thread: thread, to: folder) - } - - private func move(thread: Thread, to folder: Folder) async throws { - let response = try await mailboxManager.move(threads: [thread], to: folder) - IKSnackBar.showCancelableSnackBar(message: MailResourcesStrings.Localizable.snackbarThreadMoved(folder.localizedName), - cancelSuccessMessage: MailResourcesStrings.Localizable.snackbarMoveCancelled, - undoRedoAction: response, - mailboxManager: mailboxManager) - } } From 8f7be06902d0963b44bb0da0751f1ca0560e41e9 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 2 May 2023 14:10:18 +0200 Subject: [PATCH 21/30] refactor: Fix properties order --- .../Actions/ActionsPanelViewModifier.swift | 7 +++--- .../Actions/ActionsPopOverViewModifier.swift | 7 +++--- Mail/Views/Thread/MessageHeaderView.swift | 5 ++-- Mail/Views/Thread/ThreadView.swift | 25 +++++++++---------- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift index 274a8ebd3..f84b55d1a 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift @@ -30,12 +30,13 @@ struct ActionsPanelViewModifier: ViewModifier { @EnvironmentObject private var mailboxManager: MailboxManager @EnvironmentObject private var navigationStore: NavigationStore + @State private var reportJunkActionsTarget: ActionsTarget? + @State private var reportedForPhishingMessage: Message? + @State private var reportedForDisplayProblemMessage: Message? + @StateObject private var moveSheet = MoveSheet() @Binding var actionsTarget: ActionsTarget? - @State var reportJunkActionsTarget: ActionsTarget? - @State var reportedForPhishingMessage: Message? - @State var reportedForDisplayProblemMessage: Message? var completionHandler: (() -> Void)? diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift index ce1c30ad3..a44d5a20c 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift @@ -30,13 +30,14 @@ struct ActionsPopOverViewModifier: ViewModifier { @EnvironmentObject private var mailboxManager: MailboxManager @EnvironmentObject private var navigationStore: NavigationStore - @StateObject private var moveSheet = MoveSheet() - - @Binding var actionsTarget: ActionsTarget? @State var reportJunkActionsTarget: ActionsTarget? @State var reportedForPhishingMessage: Message? @State var reportedForDisplayProblemMessage: Message? + @StateObject private var moveSheet = MoveSheet() + + @Binding var actionsTarget: ActionsTarget? + var completionHandler: (() -> Void)? func body(content: Content) -> some View { diff --git a/Mail/Views/Thread/MessageHeaderView.swift b/Mail/Views/Thread/MessageHeaderView.swift index 33caa32fc..0d6eec308 100644 --- a/Mail/Views/Thread/MessageHeaderView.swift +++ b/Mail/Views/Thread/MessageHeaderView.swift @@ -25,17 +25,18 @@ import RealmSwift import SwiftUI struct MessageHeaderView: View { + @LazyInjectService private var matomo: MatomoUtils + @EnvironmentObject private var navigationStore: NavigationStore @EnvironmentObject private var mailboxManager: MailboxManager @State private var editedDraft: Draft? @ObservedRealmObject var message: Message + @Binding var isHeaderExpanded: Bool @Binding var isMessageExpanded: Bool - @LazyInjectService private var matomo: MatomoUtils - var body: some View { VStack(spacing: 12) { MessageHeaderSummaryView(message: message, diff --git a/Mail/Views/Thread/ThreadView.swift b/Mail/Views/Thread/ThreadView.swift index c2bb50d0a..f85c8562c 100644 --- a/Mail/Views/Thread/ThreadView.swift +++ b/Mail/Views/Thread/ThreadView.swift @@ -33,25 +33,20 @@ private struct ScrollOffsetPreferenceKey: PreferenceKey { } struct ThreadView: View { + @LazyInjectService private var matomo: MatomoUtils + + @Environment(\.isCompactWindow) private var isCompactWindow + @Environment(\.dismiss) private var dismiss + @EnvironmentObject private var splitViewManager: SplitViewManager @EnvironmentObject private var mailboxManager: MailboxManager @EnvironmentObject private var navigationStore: NavigationStore - @Environment(\.horizontalSizeClass) private var sizeClass - @Environment(\.verticalSizeClass) private var verticalSizeClass - @Environment(\.dismiss) var dismiss - - @ObservedRealmObject var thread: Thread - @State private var headerHeight: CGFloat = 0 @State private var displayNavigationTitle = false @State private var replyOrReplyAllMessage: Message? - var isCompact: Bool { - sizeClass == .compact || verticalSizeClass == .compact - } - - @LazyInjectService private var matomo: MatomoUtils + @ObservedRealmObject var thread: Thread private let toolbarActions: [Action] = [.reply, .forward, .archive, .delete] @@ -112,7 +107,11 @@ struct ThreadView: View { didTap(action: action) } .adaptivePanel(item: $replyOrReplyAllMessage) { message in - ReplyActionsView(mailboxManager: mailboxManager, message: message, messageReply: $navigationStore.messageReply) + ReplyActionsView( + mailboxManager: mailboxManager, + message: message, + messageReply: $navigationStore.messageReply + ) } } else { ToolbarButton(text: action.title, icon: action.icon) { @@ -130,7 +129,7 @@ struct ThreadView: View { } .onChange(of: thread.messages) { newMessagesList in if newMessagesList.isEmpty || thread.messageInFolderCount == 0 { - if isCompact { + if isCompactWindow { dismiss() // For iPhone } else { navigationStore.threadPath = [] // For iPad From a782ce5d56cb1dd386a64f9725fa0d4217de0197 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 2 May 2023 14:32:56 +0200 Subject: [PATCH 22/30] refactor: Prevent duplicated code in popover --- Mail/Components/ActionsPanelButton.swift | 11 +-- Mail/Utils/AdaptivePanelViewModifier.swift | 20 ----- .../Actions/ActionsPanelViewModifier.swift | 49 ++++++------ .../Actions/ActionsPopOverViewModifier.swift | 76 ------------------- .../Thread List/ThreadListSwipeAction.swift | 2 +- 5 files changed, 26 insertions(+), 132 deletions(-) delete mode 100644 Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift diff --git a/Mail/Components/ActionsPanelButton.swift b/Mail/Components/ActionsPanelButton.swift index 5f557c0b1..9664a72b7 100644 --- a/Mail/Components/ActionsPanelButton.swift +++ b/Mail/Components/ActionsPanelButton.swift @@ -32,16 +32,6 @@ struct ActionsPanelButton: View { @ViewBuilder var label: () -> Content var body: some View { - if isCompactWindow { - buttonBody - .actionsPanel(actionsTarget: $actionsTarget) - } else { - buttonBody - .actionsPopOver(actionsTarget: $actionsTarget) - } - } - - var buttonBody: some View { Button { if let message { actionsTarget = .message(message) @@ -53,5 +43,6 @@ struct ActionsPanelButton: View { } label: { label() } + .actionsPanel(actionsTarget: $actionsTarget) } } diff --git a/Mail/Utils/AdaptivePanelViewModifier.swift b/Mail/Utils/AdaptivePanelViewModifier.swift index 55b77a8e6..170e50e0a 100644 --- a/Mail/Utils/AdaptivePanelViewModifier.swift +++ b/Mail/Utils/AdaptivePanelViewModifier.swift @@ -45,23 +45,3 @@ struct AdaptivePanelViewModifier: ViewMo } } } - -extension View { - func adaptiveActions(item: Binding) -> some View { - return modifier(AdaptiveActionsViewModifier(item: item)) - } -} - -struct AdaptiveActionsViewModifier: ViewModifier { - @Environment(\.isCompactWindow) private var isCompactWindow - - @Binding var item: ActionsTarget? - - func body(content: Content) -> some View { - if isCompactWindow { - content.actionsPanel(actionsTarget: $item) - } else { - content.actionsPopOver(actionsTarget: $item) - } - } -} diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift index f84b55d1a..363425ada 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift @@ -41,32 +41,31 @@ struct ActionsPanelViewModifier: ViewModifier { var completionHandler: (() -> Void)? func body(content: Content) -> some View { - content - .floatingPanel(item: $actionsTarget) { target in - ActionsView(mailboxManager: mailboxManager, - target: target, - moveSheet: moveSheet, - messageReply: $navigationStore.messageReply, - reportJunkActionsTarget: $reportJunkActionsTarget, - reportedForDisplayProblemMessage: $reportedForDisplayProblemMessage) { - completionHandler?() - } + content.adaptivePanel(item: $actionsTarget) { target in + ActionsView(mailboxManager: mailboxManager, + target: target, + moveSheet: moveSheet, + messageReply: $navigationStore.messageReply, + reportJunkActionsTarget: $reportJunkActionsTarget, + reportedForDisplayProblemMessage: $reportedForDisplayProblemMessage) { + completionHandler?() } - .sheet(isPresented: $moveSheet.isShowing) { - if case .move(let folderId, let handler) = moveSheet.state { - MoveEmailView.sheetView(from: folderId, moveHandler: handler) - } - } - .floatingPanel(item: $reportJunkActionsTarget) { target in - ReportJunkView(mailboxManager: mailboxManager, - target: target, - reportedForPhishingMessage: $reportedForPhishingMessage) - } - .customAlert(item: $reportedForPhishingMessage) { message in - ReportPhishingView(message: message) - } - .customAlert(item: $reportedForDisplayProblemMessage) { message in - ReportDisplayProblemView(message: message) + } + .sheet(isPresented: $moveSheet.isShowing) { + if case .move(let folderId, let handler) = moveSheet.state { + MoveEmailView.sheetView(from: folderId, moveHandler: handler) } + } + .floatingPanel(item: $reportJunkActionsTarget) { target in + ReportJunkView(mailboxManager: mailboxManager, + target: target, + reportedForPhishingMessage: $reportedForPhishingMessage) + } + .customAlert(item: $reportedForPhishingMessage) { message in + ReportPhishingView(message: message) + } + .customAlert(item: $reportedForDisplayProblemMessage) { message in + ReportDisplayProblemView(message: message) + } } } diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift deleted file mode 100644 index a44d5a20c..000000000 --- a/Mail/Views/Bottom sheets/Actions/ActionsPopOverViewModifier.swift +++ /dev/null @@ -1,76 +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 MailCore -import SwiftUI - -extension View { - func actionsPopOver(actionsTarget: Binding, completionHandler: (() -> Void)? = nil) -> some View { - return modifier(ActionsPopOverViewModifier(actionsTarget: actionsTarget, completionHandler: completionHandler)) - } -} - -struct ActionsPopOverViewModifier: ViewModifier { - @EnvironmentObject private var mailboxManager: MailboxManager - @EnvironmentObject private var navigationStore: NavigationStore - - @State var reportJunkActionsTarget: ActionsTarget? - @State var reportedForPhishingMessage: Message? - @State var reportedForDisplayProblemMessage: Message? - - @StateObject private var moveSheet = MoveSheet() - - @Binding var actionsTarget: ActionsTarget? - - var completionHandler: (() -> Void)? - - func body(content: Content) -> some View { - content - .popover(item: $actionsTarget) { target in - ScrollView { - ActionsView(mailboxManager: mailboxManager, - target: target, - moveSheet: moveSheet, - messageReply: $navigationStore.messageReply, - reportJunkActionsTarget: $reportJunkActionsTarget, - reportedForDisplayProblemMessage: $reportedForDisplayProblemMessage) { - completionHandler?() - } - .padding(.vertical) - } - .frame(idealWidth: 400) - } - .sheet(isPresented: $moveSheet.isShowing) { - if case .move(let folderId, let handler) = moveSheet.state { - MoveEmailView.sheetView(from: folderId, moveHandler: handler) - } - } - .floatingPanel(item: $reportJunkActionsTarget) { target in - ReportJunkView(mailboxManager: mailboxManager, - target: target, - reportedForPhishingMessage: $reportedForPhishingMessage) - } - .customAlert(item: $reportedForPhishingMessage) { message in - ReportPhishingView(message: message) - } - .customAlert(item: $reportedForDisplayProblemMessage) { message in - ReportDisplayProblemView(message: message) - } - } -} diff --git a/Mail/Views/Thread List/ThreadListSwipeAction.swift b/Mail/Views/Thread List/ThreadListSwipeAction.swift index 2d42d57f7..6ad983904 100644 --- a/Mail/Views/Thread List/ThreadListSwipeAction.swift +++ b/Mail/Views/Thread List/ThreadListSwipeAction.swift @@ -130,7 +130,7 @@ struct ThreadListSwipeActions: ViewModifier { .swipeActions(edge: .trailing) { edgeActions([swipeFullTrailing, swipeTrailing]) } - .adaptiveActions(item: $actionsTarget) + .actionsPanel(actionsTarget: $actionsTarget) .sheet(isPresented: $moveSheet.isShowing) { if case .move(let folderId, let handler) = moveSheet.state { MoveEmailView.sheetView(from: folderId, moveHandler: handler) From 443dfd732a845ad7a1a7a628199c51cca7b551bc Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 2 May 2023 14:38:53 +0200 Subject: [PATCH 23/30] fix(ThreadListSwipeAction): Re-add preview --- .../Thread List/ThreadListSwipeAction.swift | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Mail/Views/Thread List/ThreadListSwipeAction.swift b/Mail/Views/Thread List/ThreadListSwipeAction.swift index 6ad983904..16ed4b79f 100644 --- a/Mail/Views/Thread List/ThreadListSwipeAction.swift +++ b/Mail/Views/Thread List/ThreadListSwipeAction.swift @@ -163,15 +163,14 @@ extension View { } } -/* - struct ThreadListSwipeAction_Previews: PreviewProvider { - static var previews: some View { - SwipeActionView(thread: PreviewHelper.sampleThread, - viewModel: ThreadListViewModel(mailboxManager: PreviewHelper.sampleMailboxManager, - folder: PreviewHelper.sampleFolder, - moveSheet: MoveSheet(), - isCompact: false), - action: .delete) - } - } - */ +struct ThreadListSwipeAction_Previews: PreviewProvider { + static var previews: some View { + SwipeActionView(moveSheet: MoveSheet(), + actionsTarget: .constant(nil), + thread: PreviewHelper.sampleThread, + viewModel: ThreadListViewModel(mailboxManager: PreviewHelper.sampleMailboxManager, + folder: PreviewHelper.sampleFolder, + isCompact: false), + action: .delete) + } +} From fe3fa71cf8abda9e9b29a57f1b5ddb36c256e5aa Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 2 May 2023 14:39:58 +0200 Subject: [PATCH 24/30] refactor: Remove useless MoveSheet --- Mail/Views/Thread List/ThreadListView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index 8b0db443d..57c8af955 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -77,7 +77,6 @@ struct ThreadListView: View { editedMessageDraft: Binding, messageReply: Binding, isCompact: Bool) { - let moveEmailSheet = MoveSheet() _editedMessageDraft = editedMessageDraft _messageReply = messageReply _viewModel = StateObject(wrappedValue: ThreadListViewModel(mailboxManager: mailboxManager, From d81e1a9bffa489d2125322676cdd6789ec4276e7 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 2 May 2023 16:29:06 +0200 Subject: [PATCH 25/30] feat: MoveEmailView without sheetstate --- .../Actions/ActionsPanelViewModifier.swift | 11 ++-- .../Bottom sheets/Actions/ActionsView.swift | 16 ++--- .../Actions/ActionsViewModel.swift | 12 ++-- .../Thread List/ThreadListSwipeAction.swift | 23 +++---- Mail/Views/Thread List/ThreadListView.swift | 6 -- Mail/Views/Thread/MoveEmailView.swift | 61 +++++++++++++++---- 6 files changed, 72 insertions(+), 57 deletions(-) diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift index 363425ada..9748d3289 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift @@ -30,12 +30,11 @@ struct ActionsPanelViewModifier: ViewModifier { @EnvironmentObject private var mailboxManager: MailboxManager @EnvironmentObject private var navigationStore: NavigationStore + @State private var moveAction: MoveAction? @State private var reportJunkActionsTarget: ActionsTarget? @State private var reportedForPhishingMessage: Message? @State private var reportedForDisplayProblemMessage: Message? - @StateObject private var moveSheet = MoveSheet() - @Binding var actionsTarget: ActionsTarget? var completionHandler: (() -> Void)? @@ -44,17 +43,15 @@ struct ActionsPanelViewModifier: ViewModifier { content.adaptivePanel(item: $actionsTarget) { target in ActionsView(mailboxManager: mailboxManager, target: target, - moveSheet: moveSheet, + moveAction: $moveAction, messageReply: $navigationStore.messageReply, reportJunkActionsTarget: $reportJunkActionsTarget, reportedForDisplayProblemMessage: $reportedForDisplayProblemMessage) { completionHandler?() } } - .sheet(isPresented: $moveSheet.isShowing) { - if case .move(let folderId, let handler) = moveSheet.state { - MoveEmailView.sheetView(from: folderId, moveHandler: handler) - } + .sheet(item: $moveAction) { moveAction in + MoveEmailView.sheetView(moveAction: moveAction) } .floatingPanel(item: $reportJunkActionsTarget) { target in ReportJunkView(mailboxManager: mailboxManager, diff --git a/Mail/Views/Bottom sheets/Actions/ActionsView.swift b/Mail/Views/Bottom sheets/Actions/ActionsView.swift index 3259f9b09..35eb9445e 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsView.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsView.swift @@ -27,7 +27,7 @@ struct ActionsView: View { init(mailboxManager: MailboxManager, target: ActionsTarget, - moveSheet: MoveSheet? = nil, + moveAction: Binding? = nil, messageReply: Binding? = nil, reportJunkActionsTarget: Binding? = nil, reportedForDisplayProblemMessage: Binding? = nil, @@ -38,13 +38,13 @@ struct ActionsView: View { } _viewModel = StateObject(wrappedValue: ActionsViewModel(mailboxManager: mailboxManager, - target: target, - moveSheet: moveSheet, - messageReply: messageReply, - reportJunkActionsTarget: reportJunkActionsTarget, - reportedForDisplayProblemMessage: reportedForDisplayProblemMessage, - matomoCategory: matomoCategory, - completionHandler: completionHandler)) + target: target, + moveAction: moveAction, + messageReply: messageReply, + reportJunkActionsTarget: reportJunkActionsTarget, + reportedForDisplayProblemMessage: reportedForDisplayProblemMessage, + matomoCategory: matomoCategory, + completionHandler: completionHandler)) } var body: some View { diff --git a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift index f7e89bcb5..556effe15 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift @@ -208,7 +208,7 @@ enum ActionsTarget: Equatable, Identifiable { @MainActor class ActionsViewModel: ObservableObject { private let mailboxManager: MailboxManager private let target: ActionsTarget - private let moveSheet: MoveSheet? + private let moveAction: Binding? private let messageReply: Binding? private let reportJunkActionsTarget: Binding? private let reportedForPhishingMessage: Binding? @@ -224,7 +224,7 @@ enum ActionsTarget: Equatable, Identifiable { init(mailboxManager: MailboxManager, target: ActionsTarget, - moveSheet: MoveSheet? = nil, + moveAction: Binding? = nil, messageReply: Binding? = nil, reportJunkActionsTarget: Binding? = nil, reportedForPhishingMessage: Binding? = nil, @@ -233,7 +233,7 @@ enum ActionsTarget: Equatable, Identifiable { completionHandler: (() -> Void)? = nil) { self.mailboxManager = mailboxManager self.target = target.freeze() - self.moveSheet = moveSheet + self.moveAction = moveAction self.messageReply = messageReply self.reportJunkActionsTarget = reportJunkActionsTarget self.reportedForPhishingMessage = reportedForPhishingMessage @@ -424,11 +424,7 @@ enum ActionsTarget: Equatable, Identifiable { folderId = message.folderId } - moveSheet?.state = .move(folderId: folderId) { folder in - Task { - try await self.move(to: folder) - } - } + moveAction?.wrappedValue = MoveAction(fromFolderId: folderId, target: target) } private func postpone() { diff --git a/Mail/Views/Thread List/ThreadListSwipeAction.swift b/Mail/Views/Thread List/ThreadListSwipeAction.swift index 16ed4b79f..4f74d93fe 100644 --- a/Mail/Views/Thread List/ThreadListSwipeAction.swift +++ b/Mail/Views/Thread List/ThreadListSwipeAction.swift @@ -28,8 +28,7 @@ private struct SwipeActionView: View { @EnvironmentObject private var mailboxManager: MailboxManager - @ObservedObject var moveSheet: MoveSheet - + @Binding var moveAction: MoveAction? @Binding var actionsTarget: ActionsTarget? let thread: Thread @@ -60,12 +59,7 @@ private struct SwipeActionView: View { case .readUnread: try await mailboxManager.toggleRead(threads: [thread]) case .move: - moveSheet.state = .move(folderId: viewModel.folder.id) { folder in - guard thread.folder != folder else { return } - Task { - try await self.move(thread: thread, to: folder) - } - } + moveAction = MoveAction(fromFolderId: viewModel.folder.id, target: .threads([thread], false)) case .favorite: try await mailboxManager.toggleStar(threads: [thread]) case .postPone: @@ -108,8 +102,7 @@ struct ThreadListSwipeActions: ViewModifier { @AppStorage(UserDefaults.shared.key(.swipeFullTrailing)) private var swipeFullTrailing = DefaultPreferences.swipeFullTrailing @AppStorage(UserDefaults.shared.key(.swipeTrailing)) private var swipeTrailing = DefaultPreferences.swipeTrailing - @StateObject private var moveSheet = MoveSheet() - + @State private var moveAction: MoveAction? @State private var actionsTarget: ActionsTarget? let thread: Thread @@ -131,10 +124,8 @@ struct ThreadListSwipeActions: ViewModifier { edgeActions([swipeFullTrailing, swipeTrailing]) } .actionsPanel(actionsTarget: $actionsTarget) - .sheet(isPresented: $moveSheet.isShowing) { - if case .move(let folderId, let handler) = moveSheet.state { - MoveEmailView.sheetView(from: folderId, moveHandler: handler) - } + .sheet(item: $moveAction) { moveAction in + MoveEmailView.sheetView(moveAction: moveAction) } } } @@ -143,7 +134,7 @@ struct ThreadListSwipeActions: ViewModifier { private func edgeActions(_ actions: [SwipeAction]) -> some View { if !multipleSelectionViewModel.isEnabled { ForEach(actions.filter { $0 != .none }, id: \.rawValue) { action in - SwipeActionView(moveSheet: moveSheet, + SwipeActionView(moveAction: $moveAction, actionsTarget: $actionsTarget, thread: thread, viewModel: viewModel, @@ -165,7 +156,7 @@ extension View { struct ThreadListSwipeAction_Previews: PreviewProvider { static var previews: some View { - SwipeActionView(moveSheet: MoveSheet(), + SwipeActionView(moveAction: .constant(nil), actionsTarget: .constant(nil), thread: PreviewHelper.sampleThread, viewModel: ThreadListViewModel(mailboxManager: PreviewHelper.sampleMailboxManager, diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index 57c8af955..d94519d59 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -24,12 +24,6 @@ import MailResources import RealmSwift import SwiftUI -class MoveSheet: SheetState { - enum State { - case move(folderId: String?, moveHandler: MoveEmailView.MoveHandler) - } -} - class FlushAlertState: Identifiable { let id = UUID() let deletedMessages: Int? diff --git a/Mail/Views/Thread/MoveEmailView.swift b/Mail/Views/Thread/MoveEmailView.swift index 7e6e087a9..1bdf6aba4 100644 --- a/Mail/Views/Thread/MoveEmailView.swift +++ b/Mail/Views/Thread/MoveEmailView.swift @@ -24,19 +24,27 @@ import MailResources import RealmSwift import SwiftUI +struct MoveAction: Identifiable { + var id: String { + return "\(target.id)\(fromFolderId ?? "")" + } + + let fromFolderId: String? + let target: ActionsTarget +} + struct MoveEmailView: View { - typealias MoveHandler = (Folder) -> Void + @LazyInjectService private var matomo: MatomoUtils @EnvironmentObject private var mailboxManager: MailboxManager + typealias MoveHandler = (Folder) -> Void + // swiftlint:disable empty_count @ObservedResults(Folder.self, where: { $0.role != .draft && $0.parents.count == 0 && $0.toolType == nil }) var folders @State private var isShowingCreateFolderAlert = false - @LazyInjectService private var matomo: MatomoUtils - - let currentFolderId: String? - let moveHandler: MoveEmailView.MoveHandler + let moveAction: MoveAction var body: some View { ScrollView { @@ -63,31 +71,60 @@ struct MoveEmailView: View { .environment(\.folderCellType, .indicator) .matomoView(view: ["MoveEmailView"]) .customAlert(isPresented: $isShowingCreateFolderAlert) { - CreateFolderView(mode: .move(moveHandler: moveHandler)) + CreateFolderView(mode: .move { newFolder in + Task { + try await move(to: newFolder) + } + }) + } + } + + private func move(to folder: Folder) async throws { + let undoRedoAction: UndoRedoAction + let snackBarMessage: String + switch moveAction.target { + case .threads(let threads, _): + guard threads.first?.folder != folder else { return } + undoRedoAction = try await mailboxManager.move(threads: threads, to: folder) + snackBarMessage = MailResourcesStrings.Localizable.snackbarThreadsMoved(folder.localizedName) + case .message(let message): + guard message.folderId != folder.id else { return } + var messages = [message] + messages.append(contentsOf: message.duplicates) + undoRedoAction = try await mailboxManager.move(messages: messages, to: folder) + snackBarMessage = MailResourcesStrings.Localizable.snackbarMessageMoved(folder.localizedName) } + + IKSnackBar.showCancelableSnackBar(message: snackBarMessage, + cancelSuccessMessage: MailResourcesStrings.Localizable.snackbarMoveCancelled, + undoRedoAction: undoRedoAction, + mailboxManager: mailboxManager) } private func listOfFolders(nestableFolders: [NestableFolder]) -> some View { ForEach(nestableFolders) { nestableFolder in - FolderCell(folder: nestableFolder, currentFolderId: currentFolderId) { folder in - moveHandler(folder) - NotificationCenter.default.post(Notification(name: Constants.dismissMoveSheetNotificationName)) + FolderCell(folder: nestableFolder, currentFolderId: moveAction.fromFolderId) { folder in + Task { + try await move(to: folder) + NotificationCenter.default.post(Notification(name: Constants.dismissMoveSheetNotificationName)) + } } } } } extension MoveEmailView { - static func sheetView(from folderId: String?, moveHandler: @escaping MoveEmailView.MoveHandler) -> some View { + static func sheetView(moveAction: MoveAction) -> some View { SheetView { - MoveEmailView(currentFolderId: folderId, moveHandler: moveHandler) + MoveEmailView(moveAction: moveAction) } } } struct MoveMessageView_Previews: PreviewProvider { static var previews: some View { - MoveEmailView(currentFolderId: nil) { _ in /* Preview */ } + MoveEmailView(moveAction: MoveAction(fromFolderId: PreviewHelper.sampleFolder.id, + target: .message(PreviewHelper.sampleMessage))) .environmentObject(PreviewHelper.sampleMailboxManager) } } From 763957267e68bda568ef13958aa3328e27a18808 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Wed, 3 May 2023 08:55:09 +0200 Subject: [PATCH 26/30] fix: Disable multiselection after selecting action --- Mail/Views/Thread List/ThreadListView.swift | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index d94519d59..d16fed9c7 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -257,6 +257,7 @@ private struct ThreadListToolbar: ViewModifier { @ObservedObject var multipleSelectionViewModel: ThreadListMultipleSelectionViewModel @State private var isShowingSwitchAccount = false + @State private var multipleSelectionActionsTarget: ActionsTarget? @EnvironmentObject var splitViewManager: SplitViewManager @EnvironmentObject var navigationDrawerState: NavigationDrawerState @@ -347,16 +348,22 @@ private struct ThreadListToolbar: ViewModifier { .disabled(action == .archive && splitViewManager.selectedFolder?.role == .archive) } - ActionsPanelButton(threads: Array(multipleSelectionViewModel.selectedItems), - isMultiSelectionEnabled: true) { - ToolbarButtonLabel(text: MailResourcesStrings.Localizable.buttonMore, - icon: MailResourcesAsset.plusActions.swiftUIImage) + ToolbarButton( + text: MailResourcesStrings.Localizable.buttonMore, + icon: MailResourcesAsset.plusActions.swiftUIImage + ) { + multipleSelectionActionsTarget = .threads(Array(multipleSelectionViewModel.selectedItems), true) } } .disabled(multipleSelectionViewModel.selectedItems.isEmpty) } } } + .actionsPanel(actionsTarget: $multipleSelectionActionsTarget) { + withAnimation { + multipleSelectionViewModel.isEnabled = false + } + } .navigationTitle( multipleSelectionViewModel.isEnabled ? MailResourcesStrings.Localizable.multipleSelectionCount(multipleSelectionViewModel.selectedItems.count) From fe3a2e389fe4518d9fb3d3f300a23d4b34c565d5 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Wed, 3 May 2023 09:39:20 +0200 Subject: [PATCH 27/30] fix(MoveEmailView): Close sheet without waiting for task completion --- Mail/Views/Thread/MoveEmailView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mail/Views/Thread/MoveEmailView.swift b/Mail/Views/Thread/MoveEmailView.swift index 1bdf6aba4..b51d97630 100644 --- a/Mail/Views/Thread/MoveEmailView.swift +++ b/Mail/Views/Thread/MoveEmailView.swift @@ -106,8 +106,8 @@ struct MoveEmailView: View { FolderCell(folder: nestableFolder, currentFolderId: moveAction.fromFolderId) { folder in Task { try await move(to: folder) - NotificationCenter.default.post(Notification(name: Constants.dismissMoveSheetNotificationName)) } + NotificationCenter.default.post(Notification(name: Constants.dismissMoveSheetNotificationName)) } } } From 1287d773d2b3bbc98f50469603964f93f9932ec9 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Wed, 3 May 2023 11:05:17 +0200 Subject: [PATCH 28/30] refactor(SheetView): This is now a SheetViewModifier (Is there something left that I didn't refactor? :pls:) --- .../Actions/ActionsPanelViewModifier.swift | 3 +- .../MailboxesManagementView.swift | 5 +- Mail/Views/Menu Drawer/MenuDrawerView.swift | 5 +- Mail/Views/Menu Drawer/MenuHeaderView.swift | 5 +- Mail/Views/SheetView.swift | 49 -------------- Mail/Views/SheetViewModifier.swift | 67 +++++++++++++++++++ .../Thread List/ThreadListSwipeAction.swift | 3 +- Mail/Views/Thread/MoveEmailView.swift | 12 +--- 8 files changed, 80 insertions(+), 69 deletions(-) delete mode 100644 Mail/Views/SheetView.swift create mode 100644 Mail/Views/SheetViewModifier.swift diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift index 9748d3289..ea02a4217 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift @@ -51,7 +51,8 @@ struct ActionsPanelViewModifier: ViewModifier { } } .sheet(item: $moveAction) { moveAction in - MoveEmailView.sheetView(moveAction: moveAction) + MoveEmailView(moveAction: moveAction) + .sheetViewStyle() } .floatingPanel(item: $reportJunkActionsTarget) { target in ReportJunkView(mailboxManager: mailboxManager, diff --git a/Mail/Views/Menu Drawer/MailboxManagement/MailboxesManagementView.swift b/Mail/Views/Menu Drawer/MailboxManagement/MailboxesManagementView.swift index e53b3b761..21b1a66a9 100644 --- a/Mail/Views/Menu Drawer/MailboxManagement/MailboxesManagementView.swift +++ b/Mail/Views/Menu Drawer/MailboxManagement/MailboxesManagementView.swift @@ -79,9 +79,8 @@ struct MailboxesManagementView: View { } } .sheet(isPresented: $isShowingSwitchAccount) { - SheetView { - AccountListView() - } + AccountListView() + .sheetViewStyle() } .sheet(isPresented: $isShowingManageAccount) { AccountView(mailboxes: mailboxes) diff --git a/Mail/Views/Menu Drawer/MenuDrawerView.swift b/Mail/Views/Menu Drawer/MenuDrawerView.swift index 3d39b4a87..092589854 100644 --- a/Mail/Views/Menu Drawer/MenuDrawerView.swift +++ b/Mail/Views/Menu Drawer/MenuDrawerView.swift @@ -192,9 +192,8 @@ struct MenuDrawerView: View { .background(MailResourcesAsset.backgroundSecondaryColor.swiftUIColor.ignoresSafeArea()) .environment(\.folderCellType, .link) .sheet(isPresented: $viewModel.isShowingHelp) { - SheetView { - HelpView() - } + HelpView() + .sheetViewStyle() } .sheet(isPresented: $viewModel.isShowingBugTracker) { BugTrackerView(isPresented: $viewModel.isShowingBugTracker) diff --git a/Mail/Views/Menu Drawer/MenuHeaderView.swift b/Mail/Views/Menu Drawer/MenuHeaderView.swift index bd141013b..96ae784ba 100644 --- a/Mail/Views/Menu Drawer/MenuHeaderView.swift +++ b/Mail/Views/Menu Drawer/MenuHeaderView.swift @@ -53,9 +53,8 @@ struct MenuHeaderView: View { .clipped() .shadow(color: MailResourcesAsset.menuDrawerShadowColor.swiftUIColor, radius: 1, x: 0, y: 2) .sheet(isPresented: $isShowingSettings) { - SheetView { - SettingsView() - } + SettingsView() + .sheetViewStyle() } } } diff --git a/Mail/Views/SheetView.swift b/Mail/Views/SheetView.swift deleted file mode 100644 index 912970b67..000000000 --- a/Mail/Views/SheetView.swift +++ /dev/null @@ -1,49 +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 SheetView: View where Content: View { - @Environment(\.dismiss) private var dismiss - - @ViewBuilder let content: Content - - var body: some View { - NavigationView { - content - .navigationBarItems(leading: Button { - dismiss() - } label: { - Label(MailResourcesStrings.Localizable.buttonClose, systemImage: "xmark") - }) - } - .onReceive(NotificationCenter.default.publisher(for: Constants.dismissMoveSheetNotificationName)) { _ in - dismiss() - } - } -} - -struct SheetView_Previews: PreviewProvider { - static var previews: some View { - SheetView { - EmptyView() - } - } -} diff --git a/Mail/Views/SheetViewModifier.swift b/Mail/Views/SheetViewModifier.swift new file mode 100644 index 000000000..c538c6fde --- /dev/null +++ b/Mail/Views/SheetViewModifier.swift @@ -0,0 +1,67 @@ +/* + 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 + +typealias DismissModalAction = () -> Void + +struct DismissModalKey: EnvironmentKey { + static let defaultValue: DismissModalAction = { /* dismiss nothing by default */ } +} + +extension EnvironmentValues { + var dismissModal: DismissModalAction { + get { + return self[DismissModalKey.self] + } + set { + self[DismissModalKey.self] = newValue + } + } +} + +extension View { + func sheetViewStyle() -> some View { + modifier(SheetViewModifier()) + } +} + +struct SheetViewModifier: ViewModifier { + @Environment(\.dismiss) private var dismiss + + func body(content: Content) -> some View { + NavigationView { + content + .environment(\.dismissModal) { + dismiss() + } + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button { + dismiss() + } label: { + Label(MailResourcesStrings.Localizable.buttonClose, systemImage: "xmark") + } + } + } + } + .navigationViewStyle(.stack) + } +} diff --git a/Mail/Views/Thread List/ThreadListSwipeAction.swift b/Mail/Views/Thread List/ThreadListSwipeAction.swift index 4f74d93fe..cdab919a1 100644 --- a/Mail/Views/Thread List/ThreadListSwipeAction.swift +++ b/Mail/Views/Thread List/ThreadListSwipeAction.swift @@ -125,7 +125,8 @@ struct ThreadListSwipeActions: ViewModifier { } .actionsPanel(actionsTarget: $actionsTarget) .sheet(item: $moveAction) { moveAction in - MoveEmailView.sheetView(moveAction: moveAction) + MoveEmailView(moveAction: moveAction) + .sheetViewStyle() } } } diff --git a/Mail/Views/Thread/MoveEmailView.swift b/Mail/Views/Thread/MoveEmailView.swift index b51d97630..f2ca61831 100644 --- a/Mail/Views/Thread/MoveEmailView.swift +++ b/Mail/Views/Thread/MoveEmailView.swift @@ -36,6 +36,8 @@ struct MoveAction: Identifiable { struct MoveEmailView: View { @LazyInjectService private var matomo: MatomoUtils + @Environment(\.dismissModal) var dismissModal + @EnvironmentObject private var mailboxManager: MailboxManager typealias MoveHandler = (Folder) -> Void @@ -107,20 +109,12 @@ struct MoveEmailView: View { Task { try await move(to: folder) } - NotificationCenter.default.post(Notification(name: Constants.dismissMoveSheetNotificationName)) + dismissModal() } } } } -extension MoveEmailView { - static func sheetView(moveAction: MoveAction) -> some View { - SheetView { - MoveEmailView(moveAction: moveAction) - } - } -} - struct MoveMessageView_Previews: PreviewProvider { static var previews: some View { MoveEmailView(moveAction: MoveAction(fromFolderId: PreviewHelper.sampleFolder.id, From 9b603f18963084d04ae84b263b69fe33f625ab06 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Wed, 3 May 2023 12:56:25 +0200 Subject: [PATCH 29/30] fix(ActionsViewModel): Also dispatch moveAction --- Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift index 556effe15..7425ce97f 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift @@ -418,13 +418,15 @@ enum ActionsTarget: Equatable, Identifiable { private func move() { let folderId: String? switch target { - case let .threads(threads, _): + case .threads(let threads, _): folderId = threads.first?.folder?.id - case let .message(message): + case .message(let message): folderId = message.folderId } - moveAction?.wrappedValue = MoveAction(fromFolderId: folderId, target: target) + DispatchQueue.main.async { [weak self, target] in + self?.moveAction?.wrappedValue = MoveAction(fromFolderId: folderId, target: target) + } } private func postpone() { From 9e8557db4b7a88129787e4752d787fe7204d9565 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Wed, 3 May 2023 15:49:10 +0200 Subject: [PATCH 30/30] refactor: Prevent move duplication --- .../Bottom sheets/Actions/ActionUtils.swift | 54 +++++++++++++++++++ .../Actions/ActionsViewModel.swift | 35 ++---------- Mail/Views/Thread/MoveEmailView.swift | 27 ++-------- 3 files changed, 61 insertions(+), 55 deletions(-) create mode 100644 Mail/Views/Bottom sheets/Actions/ActionUtils.swift diff --git a/Mail/Views/Bottom sheets/Actions/ActionUtils.swift b/Mail/Views/Bottom sheets/Actions/ActionUtils.swift new file mode 100644 index 000000000..1f1074b6d --- /dev/null +++ b/Mail/Views/Bottom sheets/Actions/ActionUtils.swift @@ -0,0 +1,54 @@ +/* + 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 InfomaniakCoreUI +import MailResources +import MailCore + +struct ActionUtils { + let actionsTarget: ActionsTarget + let mailboxManager: MailboxManager + + func move(to folder: Folder) async throws { + let undoRedoAction: UndoRedoAction + let snackBarMessage: String + switch actionsTarget { + case .threads(let threads, _): + guard threads.first?.folder != folder else { return } + undoRedoAction = try await mailboxManager.move(threads: threads, to: folder) + snackBarMessage = MailResourcesStrings.Localizable.snackbarThreadsMoved(folder.localizedName) + case .message(let message): + guard message.folderId != folder.id else { return } + var messages = [message] + messages.append(contentsOf: message.duplicates) + undoRedoAction = try await mailboxManager.move(messages: messages, to: folder) + snackBarMessage = MailResourcesStrings.Localizable.snackbarMessageMoved(folder.localizedName) + } + + await IKSnackBar.showCancelableSnackBar(message: snackBarMessage, + cancelSuccessMessage: MailResourcesStrings.Localizable.snackbarMoveCancelled, + undoRedoAction: undoRedoAction, + mailboxManager: mailboxManager) + } + + func move(to folderRole: FolderRole) async throws { + guard let folder = mailboxManager.getFolder(with: folderRole)?.freeze() else { return } + try await move(to: folder) + } +} diff --git a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift index fb999923e..129e54514 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift @@ -317,7 +317,7 @@ enum ActionsTarget: Equatable, Identifiable { case .replyAll: try await reply(mode: .replyAll) case .archive: - try await move(to: .archive) + try await ActionUtils(actionsTarget: target, mailboxManager: mailboxManager).move(to: .archive) case .forward: try await reply(mode: .forward) case .markAsRead, .markAsUnread: @@ -331,9 +331,9 @@ enum ActionsTarget: Equatable, Identifiable { case .reportJunk: displayReportJunk() case .spam: - try await move(to: .spam) + try await ActionUtils(actionsTarget: target, mailboxManager: mailboxManager).move(to: .spam) case .nonSpam: - try await move(to: .inbox) + try await ActionUtils(actionsTarget: target, mailboxManager: mailboxManager).move(to: .inbox) case .block: try await block() case .phishing: @@ -345,40 +345,13 @@ enum ActionsTarget: Equatable, Identifiable { case .editMenu: editMenu() case .moveToInbox: - try await move(to: .inbox) + try await ActionUtils(actionsTarget: target, mailboxManager: mailboxManager).move(to: .inbox) default: print("Warning: Unhandled action!") } completionHandler?() } - private func move(to folder: Folder) async throws { - let undoRedoAction: UndoRedoAction - let snackBarMessage: String - switch target { - case .threads(let threads, _): - guard threads.first?.folder != folder else { return } - undoRedoAction = try await mailboxManager.move(threads: threads, to: folder) - snackBarMessage = MailResourcesStrings.Localizable.snackbarThreadsMoved(folder.localizedName) - case .message(let message): - guard message.folderId != folder.id else { return } - var messages = [message] - messages.append(contentsOf: message.duplicates) - undoRedoAction = try await mailboxManager.move(messages: messages, to: folder) - snackBarMessage = MailResourcesStrings.Localizable.snackbarMessageMoved(folder.localizedName) - } - - IKSnackBar.showCancelableSnackBar(message: snackBarMessage, - cancelSuccessMessage: MailResourcesStrings.Localizable.snackbarMoveCancelled, - undoRedoAction: undoRedoAction, - mailboxManager: mailboxManager) - } - - private func move(to folderRole: FolderRole) async throws { - guard let folder = mailboxManager.getFolder(with: folderRole)?.freeze() else { return } - try await move(to: folder) - } - // MARK: - Actions methods private func delete() async throws { diff --git a/Mail/Views/Thread/MoveEmailView.swift b/Mail/Views/Thread/MoveEmailView.swift index f2ca61831..b91fe5da1 100644 --- a/Mail/Views/Thread/MoveEmailView.swift +++ b/Mail/Views/Thread/MoveEmailView.swift @@ -75,39 +75,18 @@ struct MoveEmailView: View { .customAlert(isPresented: $isShowingCreateFolderAlert) { CreateFolderView(mode: .move { newFolder in Task { - try await move(to: newFolder) + try await ActionUtils(actionsTarget: moveAction.target, mailboxManager: mailboxManager).move(to: newFolder) } + dismissModal() }) } } - private func move(to folder: Folder) async throws { - let undoRedoAction: UndoRedoAction - let snackBarMessage: String - switch moveAction.target { - case .threads(let threads, _): - guard threads.first?.folder != folder else { return } - undoRedoAction = try await mailboxManager.move(threads: threads, to: folder) - snackBarMessage = MailResourcesStrings.Localizable.snackbarThreadsMoved(folder.localizedName) - case .message(let message): - guard message.folderId != folder.id else { return } - var messages = [message] - messages.append(contentsOf: message.duplicates) - undoRedoAction = try await mailboxManager.move(messages: messages, to: folder) - snackBarMessage = MailResourcesStrings.Localizable.snackbarMessageMoved(folder.localizedName) - } - - IKSnackBar.showCancelableSnackBar(message: snackBarMessage, - cancelSuccessMessage: MailResourcesStrings.Localizable.snackbarMoveCancelled, - undoRedoAction: undoRedoAction, - mailboxManager: mailboxManager) - } - private func listOfFolders(nestableFolders: [NestableFolder]) -> some View { ForEach(nestableFolders) { nestableFolder in FolderCell(folder: nestableFolder, currentFolderId: moveAction.fromFolderId) { folder in Task { - try await move(to: folder) + try await ActionUtils(actionsTarget: moveAction.target, mailboxManager: mailboxManager).move(to: folder) } dismissModal() }