Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 24 additions & 13 deletions Sources/FlowStack/FlowStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ public struct FlowStack<Root: View, Overlay: View>: View {
private var overlay: () -> Overlay

private var usesInternalPath: Bool = false
private var dismissAction: (() async -> Void)?

@StateObject private var destinationLookup: DestinationLookup = .init()
@StateObject var accessibilityManager: AccessibilityManager = .init()
Expand All @@ -236,13 +237,15 @@ public struct FlowStack<Root: View, Overlay: View>: View {
/// - Parameters:
/// - overlayAlignment: The alignment applied to the overlay.
/// - customSmoothAnimation: The animation to use during flow transitions.
/// - dismissAction: An optional asynchronous action to finish before dismissing a destination.
/// - root: The view to display when the stack is empty.
/// - overlay: The view to overlay on the FlowStack. This view is always visible in front of any view presented by the flow stack.
public init(overlayAlignment: Alignment = .center, customSmoothAnimation: CustomSmoothAnimation? = nil, @ViewBuilder root: @escaping () -> Root, @ViewBuilder overlay: @escaping () -> Overlay) {
public init(overlayAlignment: Alignment = .center, customSmoothAnimation: CustomSmoothAnimation? = nil, dismissAction: (() async -> Void)? = nil, @ViewBuilder root: @escaping () -> Root, @ViewBuilder overlay: @escaping () -> Overlay) {
self.root = root
self.overlay = overlay
self.overlayAlignment = overlayAlignment
self.customSmoothAnimation = customSmoothAnimation ?? CustomSmoothAnimation.default
self.dismissAction = dismissAction

self.usesInternalPath = true
self._path = Binding(get: { FlowPath() }, set: { _ in })
Expand All @@ -253,14 +256,16 @@ public struct FlowStack<Root: View, Overlay: View>: View {
/// - path: A Binding to the flow path for this stack.
/// - overlayAlignment: The alignment applied to the overlay.
/// - customSmoothAnimation: The animation to use during flow transitions.
/// - dismissAction: An optional asynchronous action to finish before dismissing a destination.
/// - root: The view to display when the stack is empty.
/// - overlay: The view to overlay on the FlowStack. This view is always visible in front of any view presented by the flow stack.
public init(path: Binding<FlowPath>, overlayAlignment: Alignment = .center, customSmoothAnimation: CustomSmoothAnimation? = nil, @ViewBuilder root: @escaping () -> Root, @ViewBuilder overlay: @escaping () -> Overlay) {
public init(path: Binding<FlowPath>, overlayAlignment: Alignment = .center, customSmoothAnimation: CustomSmoothAnimation? = nil, dismissAction: (() async -> Void)? = nil, @ViewBuilder root: @escaping () -> Root, @ViewBuilder overlay: @escaping () -> Overlay) {
self.root = root
self.overlay = overlay
self.overlayAlignment = overlayAlignment
self._path = path
self.customSmoothAnimation = customSmoothAnimation ?? CustomSmoothAnimation.default
self.dismissAction = dismissAction
}

private func destination(for instance: any (Hashable & Equatable)) -> AnyDestination? {
Expand Down Expand Up @@ -296,15 +301,17 @@ public struct FlowStack<Root: View, Overlay: View>: View {
private var flowDismissAction: FlowDismissAction {
FlowDismissAction(
onDismiss: {
guard let element = pathToUse.wrappedValue.elements.last else { return }

// The link comes into view first, so that it is already standing in for
// the destination when the destination starts zooming back into it.
linkContexts.revealLink(for: AnyHashable(element.value), atLevel: element.index, source: element.source) {
guard pathToUse.wrappedValue.elements.last == element else { return }
withTransaction(transaction) {
accessibilityManager.decrementIndex()
pathToUse.wrappedValue.removeLast()
Task {
guard let element = pathToUse.wrappedValue.elements.last else { return }
// The link comes into view first, so that it is already standing in for
// the destination when the destination starts zooming back into it.
await dismissAction?()
linkContexts.revealLink(for: AnyHashable(element.value), atLevel: element.index, source: element.source) {
guard pathToUse.wrappedValue.elements.last == element else { return }
withTransaction(transaction) {
accessibilityManager.decrementIndex()
pathToUse.wrappedValue.removeLast()
}
}
}
})
Expand Down Expand Up @@ -373,28 +380,32 @@ public extension FlowStack where Overlay == EmptyView {
/// Creates a flow stack that manages its own navigation state.
/// - Parameters:
/// - customSmoothAnimation: The animation to use during flow transitions.
/// - dismissAction: An optional asynchronous action to finish before dismissing a destination.
/// - root: The view to display when the stack is empty.
init(customSmoothAnimation: CustomSmoothAnimation? = nil, @ViewBuilder root: @escaping () -> Root) {
init(customSmoothAnimation: CustomSmoothAnimation? = nil, dismissAction: (() async -> Void)? = nil, @ViewBuilder root: @escaping () -> Root) {
self.root = root
self.overlay = { EmptyView() }
self.overlayAlignment = .center

self.usesInternalPath = true
self._path = Binding(get: { FlowPath() }, set: { _ in })
self.customSmoothAnimation = customSmoothAnimation ?? CustomSmoothAnimation.default
self.dismissAction = dismissAction
}

/// Creates a flow stack with heterogeneous navigation state that you can control.
/// - Parameters:
/// - path: A Binding to the flow path for this stack.
/// - customSmoothAnimation: The animation to use during flow transitions.
/// - dismissAction: An optional asynchronous action to finish before dismissing a destination.
/// - root: The view to display when the stack is empty.
init(path: Binding<FlowPath>, customSmoothAnimation: CustomSmoothAnimation? = nil, @ViewBuilder root: @escaping () -> Root) {
init(path: Binding<FlowPath>, customSmoothAnimation: CustomSmoothAnimation? = nil, dismissAction: (() async -> Void)? = nil, @ViewBuilder root: @escaping () -> Root) {
self.root = root
self.overlay = { EmptyView() }
self.overlayAlignment = .center
self._path = path
self.customSmoothAnimation = customSmoothAnimation ?? CustomSmoothAnimation.default
self.dismissAction = dismissAction
}
}

Expand Down