react-native-readium 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +135 -0
- package/android/build.gradle +198 -0
- package/android/gradle.properties +3 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/reactnativereadium/ReadiumPackage.kt +17 -0
- package/android/src/main/java/com/reactnativereadium/ReadiumView.kt +84 -0
- package/android/src/main/java/com/reactnativereadium/ReadiumViewManager.kt +80 -0
- package/android/src/main/java/com/reactnativereadium/epub/UserSettings.kt +236 -0
- package/android/src/main/java/com/reactnativereadium/reader/BaseReaderFragment.kt +81 -0
- package/android/src/main/java/com/reactnativereadium/reader/EpubReaderFragment.kt +375 -0
- package/android/src/main/java/com/reactnativereadium/reader/ImageReaderFragment.kt +68 -0
- package/android/src/main/java/com/reactnativereadium/reader/PdfReaderFragment.kt +82 -0
- package/android/src/main/java/com/reactnativereadium/reader/ReaderService.kt +116 -0
- package/android/src/main/java/com/reactnativereadium/reader/ReaderViewModel.kt +120 -0
- package/android/src/main/java/com/reactnativereadium/reader/VisualReaderFragment.kt +78 -0
- package/android/src/main/java/com/reactnativereadium/search/SearchFragment.kt +100 -0
- package/android/src/main/java/com/reactnativereadium/search/SearchPagingSource.kt +44 -0
- package/android/src/main/java/com/reactnativereadium/search/SearchResultAdapter.kt +68 -0
- package/android/src/main/java/com/reactnativereadium/utils/ContentResolverUtil.kt +156 -0
- package/android/src/main/java/com/reactnativereadium/utils/Dimensions.kt +6 -0
- package/android/src/main/java/com/reactnativereadium/utils/EventChannel.kt +61 -0
- package/android/src/main/java/com/reactnativereadium/utils/FragmentFactory.kt +46 -0
- package/android/src/main/java/com/reactnativereadium/utils/R2DispatcherActivity.kt +45 -0
- package/android/src/main/java/com/reactnativereadium/utils/SectionDecoration.kt +98 -0
- package/android/src/main/java/com/reactnativereadium/utils/SingleClickListener.kt +32 -0
- package/android/src/main/java/com/reactnativereadium/utils/SystemUiManagement.kt +63 -0
- package/android/src/main/java/com/reactnativereadium/utils/extensions/Bitmap.kt +23 -0
- package/android/src/main/java/com/reactnativereadium/utils/extensions/Context.kt +16 -0
- package/android/src/main/java/com/reactnativereadium/utils/extensions/File.kt +22 -0
- package/android/src/main/java/com/reactnativereadium/utils/extensions/InputStream.kt +23 -0
- package/android/src/main/java/com/reactnativereadium/utils/extensions/Link.kt +6 -0
- package/android/src/main/java/com/reactnativereadium/utils/extensions/Metadata.kt +6 -0
- package/android/src/main/java/com/reactnativereadium/utils/extensions/URL.kt +29 -0
- package/android/src/main/java/com/reactnativereadium/utils/extensions/Uri.kt +17 -0
- package/android/src/main/res/drawable/background_action_mode.xml +6 -0
- package/android/src/main/res/drawable/cnl.png +0 -0
- package/android/src/main/res/drawable/cover.png +0 -0
- package/android/src/main/res/drawable/ic_add_white_24dp.xml +9 -0
- package/android/src/main/res/drawable/ic_baseline_arrow_forward_24.xml +5 -0
- package/android/src/main/res/drawable/ic_baseline_bookmark_24.xml +10 -0
- package/android/src/main/res/drawable/ic_baseline_delete_24.xml +10 -0
- package/android/src/main/res/drawable/ic_baseline_edit_24.xml +10 -0
- package/android/src/main/res/drawable/ic_baseline_enhanced_encryption_24.xml +10 -0
- package/android/src/main/res/drawable/ic_baseline_fast_forward_24.xml +10 -0
- package/android/src/main/res/drawable/ic_baseline_fast_rewind_24.xml +10 -0
- package/android/src/main/res/drawable/ic_baseline_headphones_24.xml +10 -0
- package/android/src/main/res/drawable/ic_baseline_pause_24.xml +10 -0
- package/android/src/main/res/drawable/ic_baseline_play_arrow_24.xml +10 -0
- package/android/src/main/res/drawable/ic_baseline_search_24.xml +10 -0
- package/android/src/main/res/drawable/ic_baseline_skip_next_24.xml +10 -0
- package/android/src/main/res/drawable/ic_baseline_skip_previous_24.xml +10 -0
- package/android/src/main/res/drawable/ic_dashboard_black_24dp.xml +9 -0
- package/android/src/main/res/drawable/ic_fastforward_30.xml +7 -0
- package/android/src/main/res/drawable/ic_info_black_24dp.xml +9 -0
- package/android/src/main/res/drawable/ic_local_library_black_24dp.xml +9 -0
- package/android/src/main/res/drawable/ic_notch.xml +4 -0
- package/android/src/main/res/drawable/ic_outline_add_24.xml +10 -0
- package/android/src/main/res/drawable/ic_outline_format_align_justify_24.xml +10 -0
- package/android/src/main/res/drawable/ic_outline_format_align_left_24.xml +11 -0
- package/android/src/main/res/drawable/ic_outline_format_size_24.xml +10 -0
- package/android/src/main/res/drawable/ic_outline_light_mode_24.xml +10 -0
- package/android/src/main/res/drawable/ic_outline_menu_24.xml +10 -0
- package/android/src/main/res/drawable/ic_outline_remove_24.xml +10 -0
- package/android/src/main/res/drawable/ic_outline_wb_sunny_24.xml +10 -0
- package/android/src/main/res/drawable/ic_rewind_30.xml +7 -0
- package/android/src/main/res/drawable/icon_font_decrease.png +0 -0
- package/android/src/main/res/drawable/icon_font_increase.png +0 -0
- package/android/src/main/res/drawable/icon_overflow.png +0 -0
- package/android/src/main/res/drawable/rbtn_selector.xml +25 -0
- package/android/src/main/res/drawable/rbtn_textcolor_selector.xml +16 -0
- package/android/src/main/res/drawable/repfr.png +0 -0
- package/android/src/main/res/drawable/selector_blue.xml +42 -0
- package/android/src/main/res/drawable/selector_green.xml +42 -0
- package/android/src/main/res/drawable/selector_purple.xml +42 -0
- package/android/src/main/res/drawable/selector_red.xml +42 -0
- package/android/src/main/res/drawable/selector_yellow.xml +41 -0
- package/android/src/main/res/layout/activity_epub.xml +23 -0
- package/android/src/main/res/layout/activity_main.xml +32 -0
- package/android/src/main/res/layout/activity_reader.xml +6 -0
- package/android/src/main/res/layout/add_catalog_dialog.xml +25 -0
- package/android/src/main/res/layout/filter_row.xml +34 -0
- package/android/src/main/res/layout/filter_window.xml +24 -0
- package/android/src/main/res/layout/fragment_about.xml +150 -0
- package/android/src/main/res/layout/fragment_audiobook.xml +151 -0
- package/android/src/main/res/layout/fragment_bookshelf.xml +35 -0
- package/android/src/main/res/layout/fragment_catalog.xml +56 -0
- package/android/src/main/res/layout/fragment_catalog_feed_list.xml +27 -0
- package/android/src/main/res/layout/fragment_drm_management.xml +284 -0
- package/android/src/main/res/layout/fragment_listview.xml +24 -0
- package/android/src/main/res/layout/fragment_outline.xml +31 -0
- package/android/src/main/res/layout/fragment_publication_detail.xml +55 -0
- package/android/src/main/res/layout/fragment_reader.xml +6 -0
- package/android/src/main/res/layout/fragment_screen_reader.xml +143 -0
- package/android/src/main/res/layout/fragment_search.xml +39 -0
- package/android/src/main/res/layout/item_group_view.xml +22 -0
- package/android/src/main/res/layout/item_recycle_book.xml +55 -0
- package/android/src/main/res/layout/item_recycle_bookmark.xml +72 -0
- package/android/src/main/res/layout/item_recycle_button.xml +23 -0
- package/android/src/main/res/layout/item_recycle_catalog.xml +56 -0
- package/android/src/main/res/layout/item_recycle_highlight.xml +85 -0
- package/android/src/main/res/layout/item_recycle_horizontal.xml +45 -0
- package/android/src/main/res/layout/item_recycle_navigation.xml +34 -0
- package/android/src/main/res/layout/item_recycle_search.xml +14 -0
- package/android/src/main/res/layout/item_spinner_days.xml +19 -0
- package/android/src/main/res/layout/my_fragment.xml +13 -0
- package/android/src/main/res/layout/popup_delete.xml +29 -0
- package/android/src/main/res/layout/popup_note.xml +105 -0
- package/android/src/main/res/layout/popup_passphrase.xml +126 -0
- package/android/src/main/res/layout/popup_window_user_settings.xml +576 -0
- package/android/src/main/res/layout/section_header.xml +25 -0
- package/android/src/main/res/layout/view_action_mode.xml +100 -0
- package/android/src/main/res/layout/view_action_mode_reverse.xml +99 -0
- package/android/src/main/res/menu/bottom_nav_menu.xml +19 -0
- package/android/src/main/res/menu/menu_action_mode.xml +26 -0
- package/android/src/main/res/menu/menu_bookmark.xml +18 -0
- package/android/src/main/res/menu/menu_epub.xml +41 -0
- package/android/src/main/res/menu/menu_filter.xml +21 -0
- package/android/src/main/res/menu/menu_reader.xml +33 -0
- package/android/src/main/res/navigation/navigation.xml +46 -0
- package/android/src/main/res/values/arrays.xml +18 -0
- package/android/src/main/res/values/colors.xml +22 -0
- package/android/src/main/res/values/refs.xml +5 -0
- package/android/src/main/res/values/strings.xml +200 -0
- package/android/src/main/res/values/styles.xml +46 -0
- package/android/src/main/res/xml/network_security_config.xml +38 -0
- package/ios/App/AppModule.swift +62 -0
- package/ios/Common/Paths.swift +52 -0
- package/ios/Common/Publication.swift +15 -0
- package/ios/Common/Toolkit/Extensions/AnyPublisher.swift +14 -0
- package/ios/Common/Toolkit/Extensions/Future.swift +16 -0
- package/ios/Common/Toolkit/Extensions/HTTPClient.swift +65 -0
- package/ios/Common/Toolkit/Extensions/Locator.swift +14 -0
- package/ios/Common/Toolkit/Extensions/String.swift +16 -0
- package/ios/Common/Toolkit/Extensions/UIImage.swift +12 -0
- package/ios/Common/Toolkit/Extensions/UIViewController.swift +19 -0
- package/ios/Common/Toolkit/ScreenOrientation.swift +13 -0
- package/ios/Data/Bookmark.swift +23 -0
- package/ios/Reader/Common/ReaderViewController.swift +309 -0
- package/ios/Reader/EPUB/AssociatedColors.swift +27 -0
- package/ios/Reader/EPUB/EPUBModule.swift +38 -0
- package/ios/Reader/EPUB/EPUBViewController.swift +79 -0
- package/ios/Reader/ReaderError.swift +25 -0
- package/ios/Reader/ReaderFormatModule.swift +27 -0
- package/ios/Reader/ReaderModule.swift +84 -0
- package/ios/Reader/ReaderService.swift +126 -0
- package/ios/Readium-Bridging-Header.h +2 -0
- package/ios/Readium.xcodeproj/project.pbxproj +293 -0
- package/ios/ReadiumView.swift +156 -0
- package/ios/ReadiumViewManager.m +10 -0
- package/ios/ReadiumViewManager.swift +9 -0
- package/lib/commonjs/enums/Appearance.js +16 -0
- package/lib/commonjs/enums/Appearance.js.map +1 -0
- package/lib/commonjs/enums/ColumnCount.js +16 -0
- package/lib/commonjs/enums/ColumnCount.js.map +1 -0
- package/lib/commonjs/enums/FontFamily.js +21 -0
- package/lib/commonjs/enums/FontFamily.js.map +1 -0
- package/lib/commonjs/enums/TextAlignment.js +15 -0
- package/lib/commonjs/enums/TextAlignment.js.map +1 -0
- package/lib/commonjs/enums/index.js +58 -0
- package/lib/commonjs/enums/index.js.map +1 -0
- package/lib/commonjs/index.js +105 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/interfaces/Dimensions.js +2 -0
- package/lib/commonjs/interfaces/Dimensions.js.map +1 -0
- package/lib/commonjs/interfaces/File.js +6 -0
- package/lib/commonjs/interfaces/File.js.map +1 -0
- package/lib/commonjs/interfaces/Locator.js +2 -0
- package/lib/commonjs/interfaces/Locator.js.map +1 -0
- package/lib/commonjs/interfaces/Settings.js +71 -0
- package/lib/commonjs/interfaces/Settings.js.map +1 -0
- package/lib/commonjs/interfaces/index.js +58 -0
- package/lib/commonjs/interfaces/index.js.map +1 -0
- package/lib/commonjs/utils/index.js +30 -0
- package/lib/commonjs/utils/index.js.map +1 -0
- package/lib/module/enums/Appearance.js +9 -0
- package/lib/module/enums/Appearance.js.map +1 -0
- package/lib/module/enums/ColumnCount.js +9 -0
- package/lib/module/enums/ColumnCount.js.map +1 -0
- package/lib/module/enums/FontFamily.js +14 -0
- package/lib/module/enums/FontFamily.js.map +1 -0
- package/lib/module/enums/TextAlignment.js +8 -0
- package/lib/module/enums/TextAlignment.js.map +1 -0
- package/lib/module/enums/index.js +5 -0
- package/lib/module/enums/index.js.map +1 -0
- package/lib/module/index.js +61 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/interfaces/Dimensions.js +2 -0
- package/lib/module/interfaces/Dimensions.js.map +1 -0
- package/lib/module/interfaces/File.js +2 -0
- package/lib/module/interfaces/File.js.map +1 -0
- package/lib/module/interfaces/Locator.js +2 -0
- package/lib/module/interfaces/Locator.js.map +1 -0
- package/lib/module/interfaces/Settings.js +61 -0
- package/lib/module/interfaces/Settings.js.map +1 -0
- package/lib/module/interfaces/index.js +5 -0
- package/lib/module/interfaces/index.js.map +1 -0
- package/lib/module/utils/index.js +17 -0
- package/lib/module/utils/index.js.map +1 -0
- package/lib/typescript/enums/Appearance.d.ts +11 -0
- package/lib/typescript/enums/ColumnCount.d.ts +5 -0
- package/lib/typescript/enums/FontFamily.d.ts +10 -0
- package/lib/typescript/enums/TextAlignment.d.ts +4 -0
- package/lib/typescript/enums/index.d.ts +4 -0
- package/lib/typescript/index.d.ts +17 -0
- package/lib/typescript/interfaces/Dimensions.d.ts +4 -0
- package/lib/typescript/interfaces/File.d.ts +11 -0
- package/lib/typescript/interfaces/Locator.d.ts +14 -0
- package/lib/typescript/interfaces/Settings.d.ts +40 -0
- package/lib/typescript/interfaces/index.d.ts +4 -0
- package/lib/typescript/utils/index.d.ts +10 -0
- package/package.json +160 -0
- package/react-native-readium.podspec +25 -0
- package/src/enums/Appearance.ts +14 -0
- package/src/enums/ColumnCount.ts +6 -0
- package/src/enums/FontFamily.ts +11 -0
- package/src/enums/TextAlignment.ts +5 -0
- package/src/enums/index.ts +4 -0
- package/src/index.tsx +78 -0
- package/src/interfaces/Dimensions.ts +4 -0
- package/src/interfaces/File.ts +14 -0
- package/src/interfaces/Locator.ts +14 -0
- package/src/interfaces/Settings.ts +85 -0
- package/src/interfaces/index.ts +4 -0
- package/src/utils/index.ts +18 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import Combine
|
|
2
|
+
import SafariServices
|
|
3
|
+
import UIKit
|
|
4
|
+
import R2Navigator
|
|
5
|
+
import R2Shared
|
|
6
|
+
import SwiftSoup
|
|
7
|
+
import WebKit
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/// This class is meant to be subclassed by each publication format view controller. It contains the shared behavior, eg. navigation bar toggling.
|
|
11
|
+
class ReaderViewController: UIViewController, Loggable {
|
|
12
|
+
|
|
13
|
+
weak var moduleDelegate: ReaderFormatModuleDelegate?
|
|
14
|
+
|
|
15
|
+
let navigator: UIViewController & Navigator
|
|
16
|
+
let publication: Publication
|
|
17
|
+
let bookId: String
|
|
18
|
+
|
|
19
|
+
private(set) var stackView: UIStackView!
|
|
20
|
+
private lazy var positionLabel = UILabel()
|
|
21
|
+
private var subscriptions = Set<AnyCancellable>()
|
|
22
|
+
private var subject = PassthroughSubject<Locator, Never>()
|
|
23
|
+
lazy var publisher = subject.eraseToAnyPublisher()
|
|
24
|
+
|
|
25
|
+
/// This regex matches any string with at least 2 consecutive letters (not limited to ASCII).
|
|
26
|
+
/// It's used when evaluating whether to display the body of a noteref referrer as the note's title.
|
|
27
|
+
/// I.e. a `*` or `1` would not be used as a title, but `on` or `好書` would.
|
|
28
|
+
private static var noterefTitleRegex: NSRegularExpression = {
|
|
29
|
+
return try! NSRegularExpression(pattern: "[\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}]{2}")
|
|
30
|
+
}()
|
|
31
|
+
|
|
32
|
+
init(
|
|
33
|
+
navigator: UIViewController & Navigator,
|
|
34
|
+
publication: Publication,
|
|
35
|
+
bookId: String
|
|
36
|
+
) {
|
|
37
|
+
self.navigator = navigator
|
|
38
|
+
self.publication = publication
|
|
39
|
+
self.bookId = bookId
|
|
40
|
+
|
|
41
|
+
super.init(nibName: nil, bundle: nil)
|
|
42
|
+
|
|
43
|
+
NotificationCenter.default.addObserver(
|
|
44
|
+
self,
|
|
45
|
+
selector: #selector(voiceOverStatusDidChange),
|
|
46
|
+
name: UIAccessibility.voiceOverStatusDidChangeNotification,
|
|
47
|
+
object: nil
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@available(*, unavailable)
|
|
52
|
+
required init?(coder aDecoder: NSCoder) {
|
|
53
|
+
fatalError("init(coder:) has not been implemented")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
deinit {
|
|
57
|
+
NotificationCenter.default.removeObserver(self)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
override func viewDidLoad() {
|
|
61
|
+
super.viewDidLoad()
|
|
62
|
+
|
|
63
|
+
view.backgroundColor = .white
|
|
64
|
+
|
|
65
|
+
updateNavigationBar(animated: false)
|
|
66
|
+
|
|
67
|
+
stackView = UIStackView(frame: view.bounds)
|
|
68
|
+
stackView.distribution = .fill
|
|
69
|
+
stackView.axis = .vertical
|
|
70
|
+
view.addSubview(stackView)
|
|
71
|
+
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
72
|
+
let topConstraint = stackView.topAnchor.constraint(equalTo: view.topAnchor)
|
|
73
|
+
// `accessibilityTopMargin` takes precedence when VoiceOver is enabled.
|
|
74
|
+
topConstraint.priority = .defaultHigh
|
|
75
|
+
NSLayoutConstraint.activate([
|
|
76
|
+
topConstraint,
|
|
77
|
+
stackView.rightAnchor.constraint(equalTo: view.rightAnchor),
|
|
78
|
+
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
79
|
+
stackView.leftAnchor.constraint(equalTo: view.leftAnchor)
|
|
80
|
+
])
|
|
81
|
+
|
|
82
|
+
addChild(navigator)
|
|
83
|
+
stackView.addArrangedSubview(navigator.view)
|
|
84
|
+
navigator.didMove(toParent: self)
|
|
85
|
+
|
|
86
|
+
stackView.addArrangedSubview(accessibilityToolbar)
|
|
87
|
+
|
|
88
|
+
positionLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
89
|
+
positionLabel.font = .systemFont(ofSize: 12)
|
|
90
|
+
positionLabel.textColor = .darkGray
|
|
91
|
+
view.addSubview(positionLabel)
|
|
92
|
+
NSLayoutConstraint.activate([
|
|
93
|
+
positionLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
|
94
|
+
positionLabel.bottomAnchor.constraint(equalTo: navigator.view.bottomAnchor, constant: -20)
|
|
95
|
+
])
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
override func willMove(toParent parent: UIViewController?) {
|
|
99
|
+
// Restore library's default UI colors
|
|
100
|
+
navigationController?.navigationBar.tintColor = .black
|
|
101
|
+
navigationController?.navigationBar.barTintColor = .white
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
// MARK: - Navigation bar
|
|
106
|
+
|
|
107
|
+
private var navigationBarHidden: Bool = true {
|
|
108
|
+
didSet {
|
|
109
|
+
updateNavigationBar()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
func toggleNavigationBar() {
|
|
114
|
+
navigationBarHidden = !navigationBarHidden
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
func updateNavigationBar(animated: Bool = true) {
|
|
118
|
+
let hidden = navigationBarHidden && !UIAccessibility.isVoiceOverRunning
|
|
119
|
+
navigationController?.setNavigationBarHidden(hidden, animated: animated)
|
|
120
|
+
setNeedsStatusBarAppearanceUpdate()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
|
124
|
+
return .slide
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
override var prefersStatusBarHidden: Bool {
|
|
128
|
+
return navigationBarHidden && !UIAccessibility.isVoiceOverRunning
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
// MARK: - Locations
|
|
133
|
+
/// FIXME: This should be implemented in a shared Navigator interface, using Locators.
|
|
134
|
+
|
|
135
|
+
var currentBookmark: Bookmark? {
|
|
136
|
+
fatalError("Not implemented")
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// MARK: - Bookmarks
|
|
140
|
+
|
|
141
|
+
@objc func bookmarkCurrentPosition() {
|
|
142
|
+
// guard let bookmark = currentBookmark else {
|
|
143
|
+
// return
|
|
144
|
+
// }
|
|
145
|
+
|
|
146
|
+
// TODO: this should call a react callback
|
|
147
|
+
// bookmarks.add(bookmark)
|
|
148
|
+
// .sink { completion in
|
|
149
|
+
// switch completion {
|
|
150
|
+
// case .finished:
|
|
151
|
+
// toast(NSLocalizedString("reader_bookmark_success_message", comment: "Success message when adding a bookmark"), on: self.view, duration: 1)
|
|
152
|
+
// case .failure(let error):
|
|
153
|
+
// print(error)
|
|
154
|
+
// toast(NSLocalizedString("reader_bookmark_failure_message", comment: "Error message when adding a new bookmark failed"), on: self.view, duration: 2)
|
|
155
|
+
// }
|
|
156
|
+
// } receiveValue: { _ in }
|
|
157
|
+
// .store(in: &subscriptions)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// MARK: - Accessibility
|
|
161
|
+
|
|
162
|
+
/// Constraint used to shift the content under the navigation bar, since it is always visible when VoiceOver is running.
|
|
163
|
+
private lazy var accessibilityTopMargin: NSLayoutConstraint = {
|
|
164
|
+
let topAnchor: NSLayoutYAxisAnchor = {
|
|
165
|
+
if #available(iOS 11.0, *) {
|
|
166
|
+
return self.view.safeAreaLayoutGuide.topAnchor
|
|
167
|
+
} else {
|
|
168
|
+
return self.topLayoutGuide.bottomAnchor
|
|
169
|
+
}
|
|
170
|
+
}()
|
|
171
|
+
return self.stackView.topAnchor.constraint(equalTo: topAnchor)
|
|
172
|
+
}()
|
|
173
|
+
|
|
174
|
+
private lazy var accessibilityToolbar: UIToolbar = {
|
|
175
|
+
func makeItem(_ item: UIBarButtonItem.SystemItem, label: String? = nil, action: UIKit.Selector? = nil) -> UIBarButtonItem {
|
|
176
|
+
let button = UIBarButtonItem(barButtonSystemItem: item, target: (action != nil) ? self : nil, action: action)
|
|
177
|
+
button.accessibilityLabel = label
|
|
178
|
+
return button
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let toolbar = UIToolbar(frame: .zero)
|
|
182
|
+
toolbar.items = [
|
|
183
|
+
makeItem(.flexibleSpace),
|
|
184
|
+
makeItem(.rewind, label: NSLocalizedString("reader_backward_a11y_label", comment: "Accessibility label to go backward in the publication"), action: #selector(goBackward)),
|
|
185
|
+
makeItem(.flexibleSpace),
|
|
186
|
+
makeItem(.fastForward, label: NSLocalizedString("reader_forward_a11y_label", comment: "Accessibility label to go forward in the publication"), action: #selector(goForward)),
|
|
187
|
+
makeItem(.flexibleSpace),
|
|
188
|
+
]
|
|
189
|
+
toolbar.isHidden = !UIAccessibility.isVoiceOverRunning
|
|
190
|
+
toolbar.tintColor = UIColor.black
|
|
191
|
+
return toolbar
|
|
192
|
+
}()
|
|
193
|
+
|
|
194
|
+
private var isVoiceOverRunning = UIAccessibility.isVoiceOverRunning
|
|
195
|
+
|
|
196
|
+
@objc private func voiceOverStatusDidChange() {
|
|
197
|
+
let isRunning = UIAccessibility.isVoiceOverRunning
|
|
198
|
+
// Avoids excessive settings refresh when the status didn't change.
|
|
199
|
+
guard isVoiceOverRunning != isRunning else {
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
isVoiceOverRunning = isRunning
|
|
203
|
+
accessibilityTopMargin.isActive = isRunning
|
|
204
|
+
accessibilityToolbar.isHidden = !isRunning
|
|
205
|
+
updateNavigationBar()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@objc private func goBackward() {
|
|
209
|
+
navigator.goBackward()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@objc private func goForward() {
|
|
213
|
+
navigator.goForward()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
extension ReaderViewController: NavigatorDelegate {
|
|
219
|
+
func navigator(_ navigator: Navigator, locationDidChange locator: Locator) {
|
|
220
|
+
subject.send(locator)
|
|
221
|
+
positionLabel.text = {
|
|
222
|
+
if let position = locator.locations.position {
|
|
223
|
+
return "\(position) / \(publication.positions.count)"
|
|
224
|
+
} else if let progression = locator.locations.totalProgression {
|
|
225
|
+
return "\(progression)%"
|
|
226
|
+
} else {
|
|
227
|
+
return nil
|
|
228
|
+
}
|
|
229
|
+
}()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
func navigator(_ navigator: Navigator, presentExternalURL url: URL) {
|
|
233
|
+
// SFSafariViewController crashes when given an URL without an HTTP scheme.
|
|
234
|
+
guard ["http", "https"].contains(url.scheme?.lowercased() ?? "") else {
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
present(SFSafariViewController(url: url), animated: true)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
func navigator(_ navigator: Navigator, presentError error: NavigatorError) {
|
|
241
|
+
moduleDelegate?.presentError(error, from: self)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
func navigator(_ navigator: Navigator, shouldNavigateToNoteAt link: Link, content: String, referrer: String?) -> Bool {
|
|
245
|
+
|
|
246
|
+
var title = referrer
|
|
247
|
+
if let t = title {
|
|
248
|
+
title = try? clean(t, .none())
|
|
249
|
+
}
|
|
250
|
+
if !suitableTitle(title) {
|
|
251
|
+
title = nil
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let content = (try? clean(content, .none())) ?? ""
|
|
255
|
+
let page =
|
|
256
|
+
"""
|
|
257
|
+
<html>
|
|
258
|
+
<head>
|
|
259
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
260
|
+
</head>
|
|
261
|
+
<body>
|
|
262
|
+
\(content)
|
|
263
|
+
</body>
|
|
264
|
+
</html>
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
let wk = WKWebView()
|
|
268
|
+
wk.loadHTMLString(page, baseURL: nil)
|
|
269
|
+
|
|
270
|
+
let vc = UIViewController()
|
|
271
|
+
vc.view = wk
|
|
272
|
+
vc.navigationItem.title = title
|
|
273
|
+
|
|
274
|
+
let nav = UINavigationController(rootViewController: vc)
|
|
275
|
+
nav.modalPresentationStyle = .formSheet
|
|
276
|
+
self.present(nav, animated: true, completion: nil)
|
|
277
|
+
|
|
278
|
+
return false
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/// Checks to ensure the title is non-nil and contains at least 2 letters.
|
|
282
|
+
func suitableTitle(_ title: String?) -> Bool {
|
|
283
|
+
guard let title = title else { return false }
|
|
284
|
+
let range = NSRange(location: 0, length: title.utf16.count)
|
|
285
|
+
let match = ReaderViewController.noterefTitleRegex.firstMatch(in: title, range: range)
|
|
286
|
+
return match != nil
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
extension ReaderViewController: VisualNavigatorDelegate {
|
|
292
|
+
|
|
293
|
+
func navigator(_ navigator: VisualNavigator, didTapAt point: CGPoint) {
|
|
294
|
+
let viewport = navigator.view.bounds
|
|
295
|
+
// Skips to previous/next pages if the tap is on the content edges.
|
|
296
|
+
let thresholdRange = 0...(0.2 * viewport.width)
|
|
297
|
+
var moved = false
|
|
298
|
+
if thresholdRange ~= point.x {
|
|
299
|
+
moved = navigator.goLeft(animated: false)
|
|
300
|
+
} else if thresholdRange ~= (viewport.maxX - point.x) {
|
|
301
|
+
moved = navigator.goRight(animated: false)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if !moved {
|
|
305
|
+
toggleNavigationBar()
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import R2Shared
|
|
3
|
+
|
|
4
|
+
class AssociatedColors {
|
|
5
|
+
|
|
6
|
+
/// Get associated colors for a specific appearance setting
|
|
7
|
+
/// - parameter appearance: The selected appearance
|
|
8
|
+
/// - Returns: A tuple with a main color and a text color
|
|
9
|
+
static func getColors(for appearance: UserProperty) -> (mainColor: UIColor, textColor: UIColor) {
|
|
10
|
+
var mainColor, textColor: UIColor
|
|
11
|
+
|
|
12
|
+
switch appearance.toString() {
|
|
13
|
+
case "readium-sepia-on":
|
|
14
|
+
mainColor = UIColor.init(red: 250/255, green: 244/255, blue: 232/255, alpha: 1)
|
|
15
|
+
textColor = UIColor.init(red: 18/255, green: 18/255, blue: 18/255, alpha: 1)
|
|
16
|
+
case "readium-night-on":
|
|
17
|
+
mainColor = UIColor.black
|
|
18
|
+
textColor = UIColor.init(red: 254/255, green: 254/255, blue: 254/255, alpha: 1)
|
|
19
|
+
default:
|
|
20
|
+
mainColor = UIColor.white
|
|
21
|
+
textColor = UIColor.black
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (mainColor, textColor)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import R2Shared
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
final class EPUBModule: ReaderFormatModule {
|
|
7
|
+
|
|
8
|
+
weak var delegate: ReaderFormatModuleDelegate?
|
|
9
|
+
|
|
10
|
+
init(delegate: ReaderFormatModuleDelegate?) {
|
|
11
|
+
self.delegate = delegate
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
var publicationFormats: [Publication.Format] {
|
|
15
|
+
return [.epub, .webpub]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func makeReaderViewController(
|
|
19
|
+
for publication: Publication,
|
|
20
|
+
locator: Locator?,
|
|
21
|
+
bookId: String,
|
|
22
|
+
resourcesServer: ResourcesServer
|
|
23
|
+
) throws -> ReaderViewController {
|
|
24
|
+
guard publication.metadata.identifier != nil else {
|
|
25
|
+
throw ReaderError.epubNotValid
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let epubViewController = EPUBViewController(
|
|
29
|
+
publication: publication,
|
|
30
|
+
locator: locator,
|
|
31
|
+
bookId: bookId,
|
|
32
|
+
resourcesServer: resourcesServer
|
|
33
|
+
)
|
|
34
|
+
epubViewController.moduleDelegate = delegate
|
|
35
|
+
return epubViewController
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import R2Shared
|
|
3
|
+
import R2Navigator
|
|
4
|
+
|
|
5
|
+
class EPUBViewController: ReaderViewController {
|
|
6
|
+
|
|
7
|
+
init(
|
|
8
|
+
publication: Publication,
|
|
9
|
+
locator: Locator?,
|
|
10
|
+
bookId: String,
|
|
11
|
+
resourcesServer: ResourcesServer
|
|
12
|
+
) {
|
|
13
|
+
let navigator = EPUBNavigatorViewController(
|
|
14
|
+
publication: publication,
|
|
15
|
+
initialLocation: locator,
|
|
16
|
+
resourcesServer: resourcesServer
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
super.init(
|
|
20
|
+
navigator: navigator,
|
|
21
|
+
publication: publication,
|
|
22
|
+
bookId: bookId
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
navigator.delegate = self
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var epubNavigator: EPUBNavigatorViewController {
|
|
29
|
+
return navigator as! EPUBNavigatorViewController
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override func viewDidLoad() {
|
|
33
|
+
super.viewDidLoad()
|
|
34
|
+
|
|
35
|
+
/// Set initial UI appearance.
|
|
36
|
+
if let appearance = publication.userProperties.getProperty(reference: ReadiumCSSReference.appearance.rawValue) {
|
|
37
|
+
setUIColor(for: appearance)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
internal func setUIColor(for appearance: UserProperty) {
|
|
42
|
+
let colors = AssociatedColors.getColors(for: appearance)
|
|
43
|
+
|
|
44
|
+
navigator.view.backgroundColor = colors.mainColor
|
|
45
|
+
view.backgroundColor = colors.mainColor
|
|
46
|
+
//
|
|
47
|
+
navigationController?.navigationBar.barTintColor = colors.mainColor
|
|
48
|
+
navigationController?.navigationBar.tintColor = colors.textColor
|
|
49
|
+
|
|
50
|
+
navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: colors.textColor]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override var currentBookmark: Bookmark? {
|
|
54
|
+
guard let locator = navigator.currentLocation else {
|
|
55
|
+
return nil
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return Bookmark(bookId: bookId, locator: locator)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
extension EPUBViewController: EPUBNavigatorDelegate {}
|
|
64
|
+
|
|
65
|
+
extension EPUBViewController: UIGestureRecognizerDelegate {
|
|
66
|
+
|
|
67
|
+
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
extension EPUBViewController: UIPopoverPresentationControllerDelegate {
|
|
74
|
+
// Prevent the popOver to be presented fullscreen on iPhones.
|
|
75
|
+
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle
|
|
76
|
+
{
|
|
77
|
+
return .none
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
enum ReaderError: LocalizedError {
|
|
4
|
+
case formatNotSupported
|
|
5
|
+
case epubNotValid
|
|
6
|
+
case openFailed(Error)
|
|
7
|
+
case fileNotFound(Error)
|
|
8
|
+
case cancelled
|
|
9
|
+
|
|
10
|
+
var errorDescription: String? {
|
|
11
|
+
switch self {
|
|
12
|
+
case .formatNotSupported:
|
|
13
|
+
return NSLocalizedString("reader_error_formatNotSupported", comment: "Error message when trying to read a publication with a unsupported format")
|
|
14
|
+
case .epubNotValid:
|
|
15
|
+
return NSLocalizedString("reader_error_epubNotValid", comment: "Error message when trying to read an EPUB that is invalid")
|
|
16
|
+
case .openFailed(let error):
|
|
17
|
+
return String(format: NSLocalizedString("reader_error_openFailed", comment: "Error message used when a low-level error occured while opening a publication"), error.localizedDescription)
|
|
18
|
+
case .fileNotFound(let error):
|
|
19
|
+
return String(format: NSLocalizedString("reader_error_openFailed", comment: "Error message used when a low-level error occured while attempting to open the specified file"), error.localizedDescription)
|
|
20
|
+
default:
|
|
21
|
+
return nil
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import R2Shared
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/// A ReaderFormatModule is a sub-module of ReaderModule that handles publication of a given format (eg. EPUB, CBZ).
|
|
7
|
+
protocol ReaderFormatModule {
|
|
8
|
+
|
|
9
|
+
var delegate: ReaderFormatModuleDelegate? { get }
|
|
10
|
+
|
|
11
|
+
/// Publication types handled by this sub-module.
|
|
12
|
+
var publicationFormats: [Publication.Format] { get }
|
|
13
|
+
|
|
14
|
+
/// Creates the view controller to present the publication.
|
|
15
|
+
func makeReaderViewController(
|
|
16
|
+
for publication: Publication,
|
|
17
|
+
locator: Locator?,
|
|
18
|
+
bookId: String,
|
|
19
|
+
resourcesServer: ResourcesServer
|
|
20
|
+
) throws -> ReaderViewController
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protocol ReaderFormatModuleDelegate: AnyObject {
|
|
25
|
+
func presentAlert(_ title: String, message: String, from viewController: UIViewController)
|
|
26
|
+
func presentError(_ error: Error?, from viewController: UIViewController)
|
|
27
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import R2Shared
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/// The ReaderModule handles the presentation of publications to be read by the user.
|
|
7
|
+
/// It contains sub-modules implementing ReaderFormatModule to handle each format of publication (eg. CBZ, EPUB).
|
|
8
|
+
protocol ReaderModuleAPI {
|
|
9
|
+
var delegate: ReaderModuleDelegate? { get }
|
|
10
|
+
|
|
11
|
+
func getViewController(
|
|
12
|
+
for publication: Publication,
|
|
13
|
+
bookId: String,
|
|
14
|
+
locator: Locator?
|
|
15
|
+
) -> ReaderViewController?
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
protocol ReaderModuleDelegate: ModuleDelegate {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
final class ReaderModule: ReaderModuleAPI {
|
|
22
|
+
weak var delegate: ReaderModuleDelegate?
|
|
23
|
+
private let resourcesServer: ResourcesServer
|
|
24
|
+
|
|
25
|
+
/// Sub-modules to handle different publication formats (eg. EPUB, CBZ)
|
|
26
|
+
var formatModules: [ReaderFormatModule] = []
|
|
27
|
+
|
|
28
|
+
init(
|
|
29
|
+
delegate: ReaderModuleDelegate?,
|
|
30
|
+
resourcesServer: ResourcesServer
|
|
31
|
+
) {
|
|
32
|
+
self.delegate = delegate
|
|
33
|
+
self.resourcesServer = resourcesServer
|
|
34
|
+
|
|
35
|
+
formatModules = [
|
|
36
|
+
// CBZModule(delegate: self),
|
|
37
|
+
EPUBModule(delegate: self),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
// TODO: add PDF reader later
|
|
41
|
+
// if #available(iOS 11.0, *) {
|
|
42
|
+
// formatModules.append(PDFModule(delegate: self))
|
|
43
|
+
// }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func getViewController(
|
|
47
|
+
for publication: Publication,
|
|
48
|
+
bookId: String,
|
|
49
|
+
locator: Locator?
|
|
50
|
+
) -> ReaderViewController? {
|
|
51
|
+
guard let module = self.formatModules.first(
|
|
52
|
+
where:{ $0.publicationFormats.contains(publication.format) }
|
|
53
|
+
) else {
|
|
54
|
+
print("Unable to display the publication due to an unsupported format.")
|
|
55
|
+
return nil
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
do {
|
|
59
|
+
return try module.makeReaderViewController(
|
|
60
|
+
for: publication,
|
|
61
|
+
locator: locator,
|
|
62
|
+
bookId: bookId,
|
|
63
|
+
resourcesServer: resourcesServer
|
|
64
|
+
)
|
|
65
|
+
} catch {
|
|
66
|
+
print("An unexpected error occurred when attempting to build the reader view.")
|
|
67
|
+
print(error)
|
|
68
|
+
return nil
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
extension ReaderModule: ReaderFormatModuleDelegate {
|
|
76
|
+
func presentAlert(_ title: String, message: String, from viewController: UIViewController) {
|
|
77
|
+
delegate?.presentAlert(title, message: message, from: viewController)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func presentError(_ error: Error?, from viewController: UIViewController) {
|
|
81
|
+
delegate?.presentError(error, from: viewController)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
}
|