diff --git a/Mail/Components/ActionsPanelButton.swift b/Mail/Components/ActionsPanelButton.swift index ad2adcc46..befd50583 100644 --- a/Mail/Components/ActionsPanelButton.swift +++ b/Mail/Components/ActionsPanelButton.swift @@ -27,6 +27,7 @@ struct ActionsPanelButton: View { let messages: [Message] let originFolder: Folder? let panelSource: ActionOrigin.FloatingPanelSource + var popoverArrowEdge: Edge = .top @ViewBuilder var label: () -> Content var body: some View { @@ -35,7 +36,12 @@ struct ActionsPanelButton: View { } label: { label() } - .actionsPanel(messages: $actionMessages, originFolder: originFolder, panelSource: panelSource) { action in + .actionsPanel( + messages: $actionMessages, + originFolder: originFolder, + panelSource: panelSource, + popoverArrowEdge: popoverArrowEdge + ) { action in if action == .markAsUnread { dismiss() } diff --git a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift index a1bea7703..5b7002755 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift @@ -27,12 +27,14 @@ extension View { messages: Binding<[Message]?>, originFolder: Folder?, panelSource: ActionOrigin.FloatingPanelSource, + popoverArrowEdge: Edge, completionHandler: ((Action) -> Void)? = nil ) -> some View { return modifier(ActionsPanelViewModifier( messages: messages, originFolder: originFolder, panelSource: panelSource, + popoverArrowEdge: popoverArrowEdge, completionHandler: completionHandler )) } @@ -55,7 +57,7 @@ struct ActionsPanelViewModifier: ViewModifier { @Binding var messages: [Message]? let originFolder: Folder? let panelSource: ActionOrigin.FloatingPanelSource - + var popoverArrowEdge: Edge var completionHandler: ((Action) -> Void)? private var origin: ActionOrigin { @@ -74,7 +76,7 @@ struct ActionsPanelViewModifier: ViewModifier { } func body(content: Content) -> some View { - content.adaptivePanel(item: $messages) { messages in + content.adaptivePanel(item: $messages, popoverArrowEdge: popoverArrowEdge) { messages in ActionsView( user: currentUser.value, target: messages, diff --git a/Mail/Views/Search/SearchModifiers.swift b/Mail/Views/Search/SearchModifiers.swift index 84baa3f09..5a5684466 100644 --- a/Mail/Views/Search/SearchModifiers.swift +++ b/Mail/Views/Search/SearchModifiers.swift @@ -141,7 +141,8 @@ struct SearchToolbar: ViewModifier { .actionsPanel( messages: $multipleSelectedMessages, originFolder: viewModel.frozenSearchFolder, - panelSource: .threadList + panelSource: .threadList, + popoverArrowEdge: .leading ) { action in viewModel.refreshSearchIfNeeded(action: action) multipleSelectionViewModel.disable() diff --git a/Mail/Views/Thread List/ThreadListModifiers.swift b/Mail/Views/Thread List/ThreadListModifiers.swift index 9a49116ca..b58e5e690 100644 --- a/Mail/Views/Thread List/ThreadListModifiers.swift +++ b/Mail/Views/Thread List/ThreadListModifiers.swift @@ -153,7 +153,8 @@ struct ThreadListToolbar: ViewModifier { .actionsPanel( messages: $multipleSelectedMessages, originFolder: viewModel.frozenFolder, - panelSource: .threadList + panelSource: .threadList, + popoverArrowEdge: .bottom ) { action in guard action != .openMovePanel else { return } multipleSelectionViewModel.disable() diff --git a/Mail/Views/Thread List/ThreadListSwipeAction.swift b/Mail/Views/Thread List/ThreadListSwipeAction.swift index ba76621c2..fadb026f2 100644 --- a/Mail/Views/Thread List/ThreadListSwipeAction.swift +++ b/Mail/Views/Thread List/ThreadListSwipeAction.swift @@ -103,7 +103,12 @@ struct ThreadListSwipeActions: ViewModifier { edgeActions([swipeFullTrailing, swipeTrailing]) } } - .actionsPanel(messages: $actionPanelMessages, originFolder: thread.folder, panelSource: .threadList) { action in + .actionsPanel( + messages: $actionPanelMessages, + originFolder: thread.folder, + panelSource: .threadList, + popoverArrowEdge: .leading + ) { action in viewModel.refreshSearchIfNeeded(action: action) } .sheet(item: $messagesToMove) { messages in diff --git a/Mail/Views/Thread/WebView/ThreadViewToolbarModifier.swift b/Mail/Views/Thread/WebView/ThreadViewToolbarModifier.swift index aee8f247f..ad2e7e23b 100644 --- a/Mail/Views/Thread/WebView/ThreadViewToolbarModifier.swift +++ b/Mail/Views/Thread/WebView/ThreadViewToolbarModifier.swift @@ -69,7 +69,7 @@ struct ThreadViewToolbarModifier: ViewModifier { ToolbarButton(text: action.title, icon: action.icon) { didTap(action: action) } - .adaptivePanel(item: $replyOrReplyAllMessage) { message in + .adaptivePanel(item: $replyOrReplyAllMessage, popoverArrowEdge: .bottom) { message in ReplyActionsView(message: message) } } else { @@ -82,7 +82,12 @@ struct ThreadViewToolbarModifier: ViewModifier { } } } - ActionsPanelButton(messages: frozenMessages, originFolder: frozenFolder, panelSource: .messageList) { + ActionsPanelButton( + messages: frozenMessages, + originFolder: frozenFolder, + panelSource: .messageList, + popoverArrowEdge: .bottom + ) { ToolbarButtonLabel(text: MailResourcesStrings.Localizable.buttonMore, icon: MailResourcesAsset.plusActions.swiftUIImage) } diff --git a/MailCoreUI/Utils/AdaptivePanelViewModifier.swift b/MailCoreUI/Utils/AdaptivePanelViewModifier.swift index 40728da7a..d1293c881 100644 --- a/MailCoreUI/Utils/AdaptivePanelViewModifier.swift +++ b/MailCoreUI/Utils/AdaptivePanelViewModifier.swift @@ -21,26 +21,26 @@ import MailResources import SwiftUI public extension View { - func adaptivePanel(item: Binding, - @ViewBuilder content: @escaping (Item) -> Content) -> some View { - return modifier(AdaptivePanelViewModifier(item: item, panelContent: content)) + func adaptivePanel( + item: Binding, + popoverArrowEdge: Edge = .top, + @ViewBuilder content: @escaping (Item) -> Content + ) -> some View { + return modifier(AdaptivePanelViewModifier(item: item, popoverArrowEdge: popoverArrowEdge, panelContent: content)) } } -public struct AdaptivePanelViewModifier: ViewModifier { +struct AdaptivePanelViewModifier: ViewModifier { @Environment(\.isCompactWindow) private var isCompactWindow @Binding var item: Item? - @ViewBuilder let panelContent: (Item) -> PanelContent - public init(item: Binding, panelContent: @escaping (Item) -> PanelContent) { - _item = item - self.panelContent = panelContent - } + var popoverArrowEdge: Edge + @ViewBuilder let panelContent: (Item) -> PanelContent - public func body(content: Content) -> some View { + func body(content: Content) -> some View { content - .popover(item: $item) { item in + .workaroundPopover(item: $item, arrowEdge: popoverArrowEdge) { item in if isCompactWindow { if #available(iOS 16.0, *) { panelContent(item).modifier(SelfSizingPanelViewModifier()) @@ -59,3 +59,44 @@ public struct AdaptivePanelViewModifier: } } } + +// MARK: - WorkaroundPopover + +private extension View { + func workaroundPopover( + item: Binding, + arrowEdge: Edge, + @ViewBuilder content: @escaping (Item) -> Content + ) -> some View { + modifier(WorkaroundPopover(item: item, arrowEdge: arrowEdge, panelContent: content)) + } +} + +// FIXME: Remove this workaround when the Release Candidate of Xcode 16.2 is release +/// iOS 18.1 introduces an issue with the popover (135231043) fixed by iOS 18.2 (the bug is also visible on iOS 18.0) +/// +/// In this specific version of iOS, the `popover` type signature has been changed. The `arrowEdge: Edge?` +/// parameter is no longer optional with the default value of nil. The default value is now `Edge.top` +/// which causes the popover to be hidden in some cases. +/// +/// Therefore for iOS 18.1 and later versions (while we build with Xcode 16.1) we have to provide a value for `arrowEdge`. +private struct WorkaroundPopover: ViewModifier { + @Binding var item: Item? + + let arrowEdge: Edge + @ViewBuilder let panelContent: (Item) -> PanelContent + + func body(content: Content) -> some View { + if #available(iOS 18.0, *) { + content + .popover(item: $item, arrowEdge: arrowEdge) { item in + panelContent(item) + } + } else { + content + .popover(item: $item) { item in + panelContent(item) + } + } + } +}