-
Notifications
You must be signed in to change notification settings - Fork 118
/
Navigator.swift
172 lines (139 loc) · 6.65 KB
/
Navigator.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//
// Copyright 2025 Readium Foundation. All rights reserved.
// Use of this source code is governed by the BSD-style license
// available in the top-level LICENSE file of the project.
//
import Foundation
import ReadiumInternal
import ReadiumShared
import SafariServices
public protocol Navigator: AnyObject {
/// Publication being rendered.
var publication: Publication { get }
/// Current position in the publication.
/// Can be used to save a bookmark to the current position.
var currentLocation: Locator? { get }
/// Moves to the position in the publication correponding to the given
/// `Locator`.
///
/// - Returns: Whether the navigator is able to move to the locator. The
/// completion block is only called if true was returned.
@discardableResult
func go(to locator: Locator, options: NavigatorGoOptions) async -> Bool
/// Moves to the position in the publication targeted by the given link.
/// - Returns: Whether the navigator is able to move to the locator. The
/// completion block is only called if true was returned.
@discardableResult
func go(to link: Link, options: NavigatorGoOptions) async -> Bool
/// Moves to the next content portion (eg. page or audiobook resource) in
/// the reading progression direction.
///
/// - Returns: Whether the navigator is able to move to the next content
/// portion. The completion block is only called if true was returned.
@discardableResult
func goForward(options: NavigatorGoOptions) async -> Bool
/// Moves to the previous content portion (eg. page or audiobook resource)
/// in the reading progression direction.
///
/// - Returns: Whether the navigator is able to move to the previous content
/// portion. The completion block is only called if true was returned.
@discardableResult
func goBackward(options: NavigatorGoOptions) async -> Bool
}
public struct NavigatorGoOptions {
/// Indicates whether the move should be animated when possible.
public var animated: Bool = false
/// Extension point for navigator implementations.
public var otherOptions: [String: Any] {
get { otherOptionsJSON.json }
set { otherOptionsJSON = JSONDictionary(newValue) ?? JSONDictionary() }
}
// Trick to keep the struct equatable despite [String: Any]
private var otherOptionsJSON: JSONDictionary
public init(animated: Bool = false, otherOptions: [String: Any] = [:]) {
self.animated = animated
otherOptionsJSON = JSONDictionary(otherOptions) ?? JSONDictionary()
}
public static var none: NavigatorGoOptions {
NavigatorGoOptions()
}
/// Convenience helper for options that contain only animated: true.
public static var animated: NavigatorGoOptions {
NavigatorGoOptions(animated: true)
}
}
public extension Navigator {
@discardableResult
func go(to locator: Locator, options: NavigatorGoOptions = NavigatorGoOptions()) async -> Bool {
await go(to: locator, options: options)
}
@discardableResult
func go(to link: Link, options: NavigatorGoOptions = NavigatorGoOptions()) async -> Bool {
await go(to: link, options: options)
}
@discardableResult
func goForward(options: NavigatorGoOptions = NavigatorGoOptions()) async -> Bool {
await goForward(options: options)
}
@discardableResult
func goBackward(options: NavigatorGoOptions = NavigatorGoOptions()) async -> Bool {
await goBackward(options: options)
}
@available(*, unavailable, message: "Use the async variant")
func go(to locator: Locator, animated: Bool = false, completion: @escaping () -> Void = {}) -> Bool {
fatalError()
}
@available(*, unavailable, message: "Use the async variant")
func go(to link: Link, animated: Bool = false, completion: @escaping () -> Void = {}) -> Bool {
fatalError()
}
@available(*, unavailable, message: "Use the async variant")
func goForward(animated: Bool = false, completion: @escaping () -> Void = {}) -> Bool {
fatalError()
}
@available(*, unavailable, message: "Use the async variant")
func goBackward(animated: Bool = false, completion: @escaping () -> Void = {}) -> Bool {
fatalError()
}
}
@MainActor public protocol NavigatorDelegate: AnyObject {
/// Called when the current position in the publication changed. You should save the locator here to restore the
/// last read page.
func navigator(_ navigator: Navigator, locationDidChange locator: Locator)
/// Called when the navigator jumps to an explicit location, which might break the linear reading progression.
///
/// For example, it is called when clicking on internal links or programmatically calling `go()`, but not when
/// turning pages.
///
/// You can use this callback to implement a navigation history by differentiating between continuous and
/// discontinuous moves.
func navigator(_ navigator: Navigator, didJumpTo locator: Locator)
/// Called when an error must be reported to the user.
func navigator(_ navigator: Navigator, presentError error: NavigatorError)
/// Called when the user tapped an external URL. The default implementation opens the URL with the default browser.
func navigator(_ navigator: Navigator, presentExternalURL url: URL)
/// Called when the user taps on a link referring to a note.
///
/// Return `true` to navigate to the note, or `false` if you intend to present the
/// note yourself, using its `content`. `link.type` contains information about the
/// format of `content` and `referrer`, such as `text/html`.
func navigator(_ navigator: Navigator, shouldNavigateToNoteAt link: Link, content: String, referrer: String?) -> Bool
/// Called when an error occurs while attempting to load a resource.
func navigator(_ navigator: Navigator, didFailToLoadResourceAt href: RelativeURL, withError error: ReadError)
}
public extension NavigatorDelegate {
func navigator(_ navigator: Navigator, didJumpTo locator: Locator) {}
func navigator(_ navigator: Navigator, presentExternalURL url: URL) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
func navigator(_ navigator: Navigator, shouldNavigateToNoteAt link: Link, content: String, referrer: String?) -> Bool {
true
}
func navigator(_ navigator: Navigator, didFailToLoadResourceAt href: String, withError error: ReadError) {}
}
public enum NavigatorError: Error {
/// The user tried to copy the text selection but the DRM License doesn't allow it.
case copyForbidden
}