Skip to content
This repository was archived by the owner on Aug 12, 2022. It is now read-only.

Commit fb6a34d

Browse files
Merge pull request #81 from readium/fixes/lcp
Various LCP fixes
2 parents cf48dc3 + 1f92974 commit fb6a34d

File tree

13 files changed

+191
-68
lines changed

13 files changed

+191
-68
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ before_install:
1616
- carthage update --verbose --no-use-binaries --platform iOS --cache-builds
1717

1818
script:
19-
- xcodebuild clean build -project r2-navigator-swift.xcodeproj -scheme r2-navigator-swift -destination "platform=iOS Simulator,name=iPhone 8,OS=11.3" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO -quiet
19+
- xcodebuild clean build -project r2-navigator-swift.xcodeproj -scheme r2-navigator-swift -quiet

Cartfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
github "readium/r2-shared-swift" == 1.4.0
1+
github "readium/r2-shared-swift" == 1.4.1

Cartfile.resolved

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
github "readium/r2-shared-swift" "1.4.0"
1+
github "readium/r2-shared-swift" "1.4.1"

R2Navigator.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
Pod::Spec.new do |s|
22

33
s.name = "R2Navigator"
4-
s.version = "1.2.0"
4+
s.version = "1.2.1"
55
s.license = "BSD 3-Clause License"
66
s.summary = "R2 Navigator"
77
s.homepage = "http://readium.github.io"
88
s.author = { "Aferdita Muriqi" => "aferdita.muriqi@gmail.com" }
9-
s.source = { :git => "https://github.com/readium/r2-navigator-swift.git", :tag => "1.2.0" }
9+
s.source = { :git => "https://github.com/readium/r2-navigator-swift.git", :tag => "1.2.1" }
1010
s.exclude_files = ["**/Info*.plist"]
1111
s.requires_arc = true
1212
s.resources = ['r2-navigator-swift/Resources/**']

r2-navigator-swift.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@
460460
PRODUCT_NAME = R2Navigator;
461461
PROVISIONING_PROFILE_SPECIFIER = "";
462462
SKIP_INSTALL = YES;
463+
SUPPORTS_MACCATALYST = NO;
463464
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
464465
SWIFT_VERSION = 5.0;
465466
};
@@ -488,6 +489,7 @@
488489
PRODUCT_NAME = R2Navigator;
489490
PROVISIONING_PROFILE_SPECIFIER = "";
490491
SKIP_INSTALL = YES;
492+
SUPPORTS_MACCATALYST = NO;
491493
SWIFT_VERSION = 5.0;
492494
};
493495
name = Release;

r2-navigator-swift/EPUB/EPUBNavigatorViewController.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,10 @@ extension EPUBNavigatorViewController: EPUBSpreadViewDelegate {
372372
notifyCurrentLocation()
373373
}
374374
}
375+
376+
func spreadView(_ spreadView: EPUBSpreadView, present viewController: UIViewController) {
377+
present(viewController, animated: true)
378+
}
375379

376380
}
377381

r2-navigator-swift/EPUB/EPUBSpreadView.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ protocol EPUBSpreadViewDelegate: class {
3232
/// Called when the pages visible in the spread changed.
3333
func spreadViewPagesDidChange(_ spreadView: EPUBSpreadView)
3434

35+
/// Called when the spread view needs to present a view controller.
36+
func spreadView(_ spreadView: EPUBSpreadView, present viewController: UIViewController)
37+
3538
}
3639

3740
class EPUBSpreadView: UIView, Loggable {
@@ -48,6 +51,7 @@ class EPUBSpreadView: UIView, Loggable {
4851
let contentLayout: ContentLayoutStyle
4952
let readingProgression: ReadingProgression
5053
let userSettings: UserSettings
54+
let editingActions: EditingActionsController
5155

5256
/// If YES, the content will be faded in once loaded.
5357
let animatedLoad: Bool
@@ -73,6 +77,7 @@ class EPUBSpreadView: UIView, Loggable {
7377
self.contentLayout = contentLayout
7478
self.readingProgression = readingProgression
7579
self.userSettings = userSettings
80+
self.editingActions = editingActions
7681
self.animatedLoad = animatedLoad
7782
self.webView = WebView(editingActions: editingActions)
7883
self.contentInset = contentInset
@@ -96,6 +101,13 @@ class EPUBSpreadView: UIView, Loggable {
96101

97102
NotificationCenter.default.addObserver(self, selector: #selector(voiceOverStatusDidChange), name: Notification.Name(UIAccessibilityVoiceOverStatusChanged), object: nil)
98103

104+
UIMenuController.shared.menuItems = [
105+
UIMenuItem(
106+
title: R2NavigatorLocalizedString("EditingAction.share"),
107+
action: #selector(shareSelection)
108+
)
109+
]
110+
99111
updateActivityIndicator()
100112
loadSpread()
101113
}
@@ -210,6 +222,34 @@ class EPUBSpreadView: UIView, Loggable {
210222
}
211223
}
212224

225+
/// Called by the JavaScript layer when the user selection changed.
226+
private func selectionDidChange(_ body: Any) {
227+
guard let selection = body as? [String: Any],
228+
let text = selection["text"] as? String,
229+
let frame = selection["frame"] as? [String: Any] else
230+
{
231+
log(.warning, "Invalid body for selectionDidChange: \(body)")
232+
return
233+
}
234+
editingActions.selectionDidChange((
235+
text: text,
236+
frame: CGRect(
237+
x: frame["x"] as? CGFloat ?? 0,
238+
y: frame["y"] as? CGFloat ?? 0,
239+
width: frame["width"] as? CGFloat ?? 0,
240+
height: frame["height"] as? CGFloat ?? 0
241+
)
242+
))
243+
}
244+
245+
/// Called when the user hit the Share item in the selection context menu.
246+
@objc func shareSelection(_ sender: Any?) {
247+
guard let shareViewController = editingActions.makeShareViewController(from: webView) else {
248+
return
249+
}
250+
delegate?.spreadView(self, present: shareViewController)
251+
}
252+
213253
/// Update webview style to userSettings.
214254
/// To override in subclasses.
215255
func applyUserSettingsStyle() {
@@ -321,6 +361,7 @@ class EPUBSpreadView: UIView, Loggable {
321361
func registerJSMessages() {
322362
registerJSMessage(named: "tap") { [weak self] in self?.didTap($0) }
323363
registerJSMessage(named: "spreadLoaded") { [weak self] in self?.spreadDidLoad($0) }
364+
registerJSMessage(named: "selectionChanged") { [weak self] in self?.selectionDidChange($0) }
324365
}
325366

326367
/// Add the message handlers for incoming javascript events.

r2-navigator-swift/EPUB/Resources/Scripts/utils.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,23 @@ var readium = (function() {
3737
ticking = true;
3838
});
3939

40+
document.addEventListener('selectionchange', debounce(50, function() {
41+
var info = {}
42+
var selection = document.getSelection();
43+
if (selection && selection.rangeCount > 0) {
44+
var rect = selection.getRangeAt(0).getBoundingClientRect();
45+
info['text'] = selection.toString().trim();
46+
info['frame'] = {
47+
'x': rect.left,
48+
'y': rect.top,
49+
'width': rect.width,
50+
'height': rect.height
51+
};
52+
}
53+
54+
webkit.messageHandlers.selectionChanged.postMessage(info);
55+
}));
56+
4057
function orientationChanged() {
4158
maxScreenX = (window.orientation === 0 || window.orientation == 180) ? screen.width : screen.height;
4259
}
@@ -146,6 +163,23 @@ var readium = (function() {
146163
}
147164

148165

166+
/// Toolkit
167+
168+
function debounce(delay, func) {
169+
var timeout;
170+
return function() {
171+
var self = this;
172+
var args = arguments;
173+
function callback() {
174+
func.apply(self, args);
175+
timeout = null;
176+
}
177+
clearTimeout(timeout);
178+
timeout = setTimeout(callback, delay);
179+
};
180+
}
181+
182+
149183
// Public API used by the navigator.
150184

151185
return {

r2-navigator-swift/EditingAction.swift

Lines changed: 61 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import R2Shared
1616

1717
public enum EditingAction: String {
1818
case copy = "copy:"
19-
case share = "_share:"
19+
case share = "shareSelection:"
2020
case lookup = "_lookup:"
2121

2222
public static var defaultActions: [EditingAction] {
@@ -36,19 +36,13 @@ protocol EditingActionsControllerDelegate: AnyObject {
3636
final class EditingActionsController {
3737

3838
public weak var delegate: EditingActionsControllerDelegate?
39-
39+
4040
private let actions: [EditingAction]
4141
private let license: DRMLicense?
4242

4343
init(actions: [EditingAction], license: DRMLicense?) {
4444
self.actions = actions
4545
self.license = license
46-
47-
NotificationCenter.default.addObserver(self, selector: #selector(pasteboardDidChange), name: UIPasteboard.changedNotification, object: nil)
48-
}
49-
50-
deinit {
51-
NotificationCenter.default.removeObserver(self)
5246
}
5347

5448
func canPerformAction(_ action: Selector) -> Bool {
@@ -61,62 +55,82 @@ final class EditingActionsController {
6155
}
6256

6357

58+
// MARK: - Selection
59+
60+
/// Current user selection contents and frame in the publication view.
61+
private var selection: (text: String, frame: CGRect)?
62+
63+
/// Peeks into the available selection contents authorized for copy.
64+
/// To be used only when required to have the contents before actually using it (eg. Share dialog). To consume the actual copy, use `copy()`.
65+
var selectionAuthorizedForCopy: (text: String, frame: CGRect)? {
66+
guard canCopy,
67+
var selection = selection else
68+
{
69+
return nil
70+
}
71+
if let license = license {
72+
guard let authorizedText = license.copy(selection.text, consumes: false) else {
73+
return nil
74+
}
75+
selection.text = authorizedText
76+
}
77+
return selection
78+
}
79+
80+
/// To be called when the user selection changed.
81+
func selectionDidChange(_ selection: (text: String, frame: CGRect)?) {
82+
self.selection = selection
83+
}
84+
85+
6486
// MARK: - Copy
6587

6688
/// Returns whether the copy interaction is at all allowed. It doesn't guarantee that the next copy action will be valid, if the license cancels it.
6789
var canCopy: Bool {
6890
return actions.contains(.copy) && (license?.canCopy ?? true)
6991
}
70-
71-
/// Called when the user attempt to copy the selection. If true is returned, then you may allow the copy.
72-
func requestCopy() -> Bool {
92+
93+
/// Copies the authorized portion of the selection text into the pasteboard.
94+
func copy() {
7395
guard canCopy else {
7496
delegate?.editingActionsDidPreventCopy(self)
75-
return false
76-
}
77-
78-
// We rely on UIPasteboardChanged to notify the copy to the delegate because UIKit sets the selection in the UIPasteboard asynchronously
79-
needsCopyCheck = true
80-
81-
return true
82-
}
83-
84-
@objc private func pasteboardDidChange() {
85-
let pasteboard = UIPasteboard.general
86-
guard needsCopyCheck, let text = pasteboard.string else {
8797
return
8898
}
89-
needsCopyCheck = false
90-
91-
guard let license = license else {
99+
guard var text = selection?.text else {
92100
return
93101
}
94-
guard license.canCopy else {
95-
pasteboard.items = []
96-
return
97-
}
98-
99-
let authorizedText = license.copy(text)
100-
if authorizedText != text {
101-
// We overwrite the pasteboard only if the authorized text is different to avoid erasing formatting
102-
pasteboard.string = authorizedText
102+
103+
if let license = license {
104+
guard let authorizedText = license.copy(text, consumes: true) else {
105+
return
106+
}
107+
text = authorizedText
103108
}
109+
110+
UIPasteboard.general.string = text
104111
}
105112

106-
private var copyTimer: Timer?
107-
private var needsCopyCheck = false {
108-
didSet {
109-
// A timer is used because we are listening to the event until the content is copied.
110-
copyTimer?.invalidate()
111-
copyTimer = nil
112-
if needsCopyCheck {
113-
copyTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(copyTimerDidFire), userInfo: nil, repeats: false)
113+
114+
// MARK: - Share
115+
116+
/// Builds a UIActivityViewController to share the authorized contents of the user selection.
117+
func makeShareViewController(from contentsView: UIView) -> UIActivityViewController? {
118+
guard canCopy else {
119+
delegate?.editingActionsDidPreventCopy(self)
120+
return nil
121+
}
122+
guard let selection = selectionAuthorizedForCopy else {
123+
return nil
124+
}
125+
let viewController = UIActivityViewController(activityItems: [selection.text], applicationActivities: nil)
126+
viewController.completionWithItemsHandler = { _, completed, _, _ in
127+
if (completed) {
128+
self.copy()
114129
}
115130
}
116-
}
117-
118-
@objc private func copyTimerDidFire() {
119-
needsCopyCheck = false
131+
viewController.popoverPresentationController?.sourceView = contentsView
132+
viewController.popoverPresentationController?.sourceRect = selection.frame
133+
return viewController
120134
}
121135

122136
}

r2-navigator-swift/PDF/PDFDocumentView.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@ public final class PDFDocumentView: PDFView {
3232
}
3333

3434
public override func copy(_ sender: Any?) {
35-
guard editingActions.requestCopy() else {
36-
return
37-
}
38-
super.copy(sender)
35+
editingActions.copy()
3936
}
40-
37+
4138
}

0 commit comments

Comments
 (0)