Skip to content

Commit 3cddff5

Browse files
authored
Connect LiveViewCoordinator from NavigationLink (#1201)
* Connect `LiveViewCoordinator` from `NavigationLink` * Support redirect navigation * Simplify `redirect` logic * Revise comment
1 parent 8e3fa92 commit 3cddff5

File tree

5 files changed

+31
-139
lines changed

5 files changed

+31
-139
lines changed

Sources/LiveViewNative/Coordinators/LiveSessionCoordinator.swift

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,21 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
7878
})
7979
$navigationPath.scan(([LiveNavigationEntry<R>](), [LiveNavigationEntry<R>]()), { ($0.1, $1) }).sink { [weak self] prev, next in
8080
guard let self else { return }
81-
if prev.count > next.count {
82-
let isDisconnected: Bool
83-
if case .notConnected = next.last!.coordinator.state {
84-
isDisconnected = true
85-
} else {
86-
isDisconnected = false
87-
}
88-
if next.last!.coordinator.url != next.last!.url || isDisconnected {
89-
Task {
81+
let isDisconnected: Bool
82+
if case .notConnected = next.last!.coordinator.state {
83+
isDisconnected = true
84+
} else {
85+
isDisconnected = false
86+
}
87+
if next.last!.coordinator.url != next.last!.url || isDisconnected {
88+
Task {
89+
if prev.count > next.count {
90+
// back navigation
9091
try await next.last!.coordinator.connect(domValues: self.domValues, redirect: true)
92+
} else if next.count > prev.count && prev.count > 0 {
93+
// forward navigation (from `redirect` or `<NavigationLink>`)
94+
await prev.last?.coordinator.disconnect()
95+
try await next.last?.coordinator.connect(domValues: self.domValues, redirect: true)
9196
}
9297
}
9398
}
@@ -359,7 +364,6 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
359364
func redirect(_ redirect: LiveRedirect) async throws {
360365
switch redirect.mode {
361366
case .replaceTop:
362-
await navigationPath.last?.coordinator.disconnect()
363367
let coordinator = LiveViewCoordinator(session: self, url: redirect.to)
364368
let entry = LiveNavigationEntry(url: redirect.to, coordinator: coordinator)
365369
switch redirect.kind {
@@ -368,27 +372,11 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
368372
case .replace:
369373
// If there is nothing to replace, change the root URL.
370374
if !navigationPath.isEmpty {
375+
await navigationPath.last?.coordinator.disconnect()
371376
navigationPath[navigationPath.count - 1] = entry
377+
try await coordinator.connect(domValues: self.domValues, redirect: true)
372378
}
373379
}
374-
try await coordinator.connect(domValues: self.domValues, redirect: true)
375-
case .multiplex:
376-
switch redirect.kind {
377-
case .push:
378-
navigationPath.append(.init(
379-
url: redirect.to,
380-
coordinator: LiveViewCoordinator(session: self, url: redirect.to)
381-
))
382-
case .replace:
383-
// If there is nothing to replace, change the root URL.
384-
navigationPath.removeLast()
385-
// If we are replacing the current path with the previous one, just pop the URL so the previous one is on top.
386-
guard (navigationPath.last?.coordinator.url ?? self.url) != redirect.to else { return }
387-
navigationPath.append(.init(
388-
url: redirect.to,
389-
coordinator: LiveViewCoordinator(session: self, url: redirect.to)
390-
))
391-
}
392380
case .patch:
393381
// patch is like `replaceTop`, but it does not disconnect.
394382
let coordinator = navigationPath.last!.coordinator

Sources/LiveViewNative/LiveView.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,15 @@ public macro LiveView<Host: LiveViewHost>(
4545
public struct LiveView<R: RootRegistry>: View {
4646
@StateObject var session: LiveSessionCoordinator<R>
4747

48-
@ObservedObject private var rootCoordinator: LiveViewCoordinator<R>
49-
5048
@StateObject private var liveViewModel = LiveViewModel()
5149

5250
@Environment(\.scenePhase) private var scenePhase
5351

5452
/// Creates a new LiveView attached to the given coordinator.
5553
///
5654
/// - Note: Changing coordinators after the `LiveView` is setup and connected is forbidden.
57-
public init(session: LiveSessionCoordinator<R>) {
58-
self._session = .init(wrappedValue: session)
59-
self.rootCoordinator = session.navigationPath.first!.coordinator
55+
public init(session: @autoclosure @escaping () -> LiveSessionCoordinator<R>) {
56+
self._session = .init(wrappedValue: session())
6057
}
6158

6259
public init(_ host: some LiveViewHost, configuration: LiveSessionConfiguration = .init()) {
@@ -66,6 +63,10 @@ public struct LiveView<R: RootRegistry>: View {
6663
public init(url: URL, configuration: LiveSessionConfiguration = .init()) {
6764
self.init(session: .init(url, config: configuration))
6865
}
66+
67+
private var rootCoordinator: LiveViewCoordinator<R> {
68+
session.navigationPath.first!.coordinator
69+
}
6970

7071
public var body: some View {
7172
SwiftUI.Group {
@@ -77,7 +78,7 @@ public struct LiveView<R: RootRegistry>: View {
7778
} else {
7879
PhxMain<R>()
7980
.environment(\.coordinatorEnvironment, CoordinatorEnvironment(rootCoordinator, document: rootCoordinator.document!))
80-
.environment(\.anyLiveContextStorage, LiveContextStorage(coordinator: rootCoordinator, url: session.url))
81+
.environment(\.anyLiveContextStorage, LiveContextStorage(coordinator: rootCoordinator, url: rootCoordinator.url))
8182
}
8283
default:
8384
if R.LoadingView.self == Never.self {
@@ -153,7 +154,7 @@ struct PhxMain<R: RootRegistry>: View {
153154
@EnvironmentObject private var session: LiveSessionCoordinator<R>
154155

155156
var body: some View {
156-
NavStackEntryView(.init(url: session.url, coordinator: context.coordinator))
157+
NavStackEntryView(.init(url: context.coordinator.url, coordinator: context.coordinator))
157158
}
158159
}
159160

Sources/LiveViewNative/Navigation/LiveRedirect.swift

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import Foundation
99

10-
struct LiveRedirect {
10+
struct LiveRedirect: Hashable {
1111
let kind: Kind
1212
let to: URL
1313
let mode: Mode
@@ -32,31 +32,6 @@ struct LiveRedirect {
3232
/// the top-most ``LiveViewCoordinator/url`` is changed to the new top-most destination.
3333
case replaceTop
3434

35-
/// Connects to a separate Phoenix channel with a fresh ``LiveViewCoordinator`` over the same WebSocket.
36-
///
37-
/// This works with `NavigationSplitView`. It can also be used with `NavigationStack` to keep the previous pages loaded in the background.
38-
///
39-
/// You must send the `native_redirect` event to use this mode:
40-
/// ```ex
41-
/// push_event(
42-
/// socket,
43-
/// "native_redirect",
44-
/// %{
45-
/// to: "destination",
46-
/// kind: :push,
47-
/// mode: :multiplex
48-
/// }
49-
/// )
50-
/// ```
51-
///
52-
/// When a redirect is received with this mode, the following takes place:
53-
///
54-
/// 1. If the kind is ``LiveRedirect/Kind/push``, a new ``LiveViewCoordinator`` is created for the redirect destination.
55-
/// A separate Phoenix channel is connected for each coordinator.
56-
/// 2. If the kind is ``LiveRedirect/Kind/redirect`` and the destination is the same as the previous path, the current entry is popped and no new ``LiveViewCoordinator`` is connected.
57-
/// Otherwise a new ``LiveViewCoordinator`` is created in place of the top-most entry.
58-
case multiplex
59-
6035
/// A `live_patch` style redirect.
6136
///
6237
/// This replaces the URL of the page without reloading anything. It can be a push or replace kind.

Sources/LiveViewNative/Navigation/NavigationCoordinator.swift

Lines changed: 0 additions & 76 deletions
This file was deleted.

Sources/LiveViewNative/Views/Layout Containers/Presentation Containers/NavigationLink.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,16 @@ struct NavigationLink<R: RootRegistry>: View {
4040
#endif
4141
@Attribute("disabled") private var disabled: Bool
4242

43+
var url: URL {
44+
URL(string: destination, relativeTo: context.coordinator.url)!.appending(path: "").absoluteURL
45+
}
46+
4347
@ViewBuilder
4448
public var body: some View {
4549
SwiftUI.NavigationLink(
4650
value: LiveNavigationEntry(
47-
url: URL(string: destination, relativeTo: context.coordinator.url)!.appending(path: "").absoluteURL,
48-
coordinator: context.coordinator
51+
url: url,
52+
coordinator: LiveViewCoordinator(session: context.coordinator.session, url: url)
4953
)
5054
) {
5155
context.buildChildren(of: element)

0 commit comments

Comments
 (0)