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.
Files changed (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +135 -0
  3. package/android/build.gradle +198 -0
  4. package/android/gradle.properties +3 -0
  5. package/android/src/main/AndroidManifest.xml +4 -0
  6. package/android/src/main/java/com/reactnativereadium/ReadiumPackage.kt +17 -0
  7. package/android/src/main/java/com/reactnativereadium/ReadiumView.kt +84 -0
  8. package/android/src/main/java/com/reactnativereadium/ReadiumViewManager.kt +80 -0
  9. package/android/src/main/java/com/reactnativereadium/epub/UserSettings.kt +236 -0
  10. package/android/src/main/java/com/reactnativereadium/reader/BaseReaderFragment.kt +81 -0
  11. package/android/src/main/java/com/reactnativereadium/reader/EpubReaderFragment.kt +375 -0
  12. package/android/src/main/java/com/reactnativereadium/reader/ImageReaderFragment.kt +68 -0
  13. package/android/src/main/java/com/reactnativereadium/reader/PdfReaderFragment.kt +82 -0
  14. package/android/src/main/java/com/reactnativereadium/reader/ReaderService.kt +116 -0
  15. package/android/src/main/java/com/reactnativereadium/reader/ReaderViewModel.kt +120 -0
  16. package/android/src/main/java/com/reactnativereadium/reader/VisualReaderFragment.kt +78 -0
  17. package/android/src/main/java/com/reactnativereadium/search/SearchFragment.kt +100 -0
  18. package/android/src/main/java/com/reactnativereadium/search/SearchPagingSource.kt +44 -0
  19. package/android/src/main/java/com/reactnativereadium/search/SearchResultAdapter.kt +68 -0
  20. package/android/src/main/java/com/reactnativereadium/utils/ContentResolverUtil.kt +156 -0
  21. package/android/src/main/java/com/reactnativereadium/utils/Dimensions.kt +6 -0
  22. package/android/src/main/java/com/reactnativereadium/utils/EventChannel.kt +61 -0
  23. package/android/src/main/java/com/reactnativereadium/utils/FragmentFactory.kt +46 -0
  24. package/android/src/main/java/com/reactnativereadium/utils/R2DispatcherActivity.kt +45 -0
  25. package/android/src/main/java/com/reactnativereadium/utils/SectionDecoration.kt +98 -0
  26. package/android/src/main/java/com/reactnativereadium/utils/SingleClickListener.kt +32 -0
  27. package/android/src/main/java/com/reactnativereadium/utils/SystemUiManagement.kt +63 -0
  28. package/android/src/main/java/com/reactnativereadium/utils/extensions/Bitmap.kt +23 -0
  29. package/android/src/main/java/com/reactnativereadium/utils/extensions/Context.kt +16 -0
  30. package/android/src/main/java/com/reactnativereadium/utils/extensions/File.kt +22 -0
  31. package/android/src/main/java/com/reactnativereadium/utils/extensions/InputStream.kt +23 -0
  32. package/android/src/main/java/com/reactnativereadium/utils/extensions/Link.kt +6 -0
  33. package/android/src/main/java/com/reactnativereadium/utils/extensions/Metadata.kt +6 -0
  34. package/android/src/main/java/com/reactnativereadium/utils/extensions/URL.kt +29 -0
  35. package/android/src/main/java/com/reactnativereadium/utils/extensions/Uri.kt +17 -0
  36. package/android/src/main/res/drawable/background_action_mode.xml +6 -0
  37. package/android/src/main/res/drawable/cnl.png +0 -0
  38. package/android/src/main/res/drawable/cover.png +0 -0
  39. package/android/src/main/res/drawable/ic_add_white_24dp.xml +9 -0
  40. package/android/src/main/res/drawable/ic_baseline_arrow_forward_24.xml +5 -0
  41. package/android/src/main/res/drawable/ic_baseline_bookmark_24.xml +10 -0
  42. package/android/src/main/res/drawable/ic_baseline_delete_24.xml +10 -0
  43. package/android/src/main/res/drawable/ic_baseline_edit_24.xml +10 -0
  44. package/android/src/main/res/drawable/ic_baseline_enhanced_encryption_24.xml +10 -0
  45. package/android/src/main/res/drawable/ic_baseline_fast_forward_24.xml +10 -0
  46. package/android/src/main/res/drawable/ic_baseline_fast_rewind_24.xml +10 -0
  47. package/android/src/main/res/drawable/ic_baseline_headphones_24.xml +10 -0
  48. package/android/src/main/res/drawable/ic_baseline_pause_24.xml +10 -0
  49. package/android/src/main/res/drawable/ic_baseline_play_arrow_24.xml +10 -0
  50. package/android/src/main/res/drawable/ic_baseline_search_24.xml +10 -0
  51. package/android/src/main/res/drawable/ic_baseline_skip_next_24.xml +10 -0
  52. package/android/src/main/res/drawable/ic_baseline_skip_previous_24.xml +10 -0
  53. package/android/src/main/res/drawable/ic_dashboard_black_24dp.xml +9 -0
  54. package/android/src/main/res/drawable/ic_fastforward_30.xml +7 -0
  55. package/android/src/main/res/drawable/ic_info_black_24dp.xml +9 -0
  56. package/android/src/main/res/drawable/ic_local_library_black_24dp.xml +9 -0
  57. package/android/src/main/res/drawable/ic_notch.xml +4 -0
  58. package/android/src/main/res/drawable/ic_outline_add_24.xml +10 -0
  59. package/android/src/main/res/drawable/ic_outline_format_align_justify_24.xml +10 -0
  60. package/android/src/main/res/drawable/ic_outline_format_align_left_24.xml +11 -0
  61. package/android/src/main/res/drawable/ic_outline_format_size_24.xml +10 -0
  62. package/android/src/main/res/drawable/ic_outline_light_mode_24.xml +10 -0
  63. package/android/src/main/res/drawable/ic_outline_menu_24.xml +10 -0
  64. package/android/src/main/res/drawable/ic_outline_remove_24.xml +10 -0
  65. package/android/src/main/res/drawable/ic_outline_wb_sunny_24.xml +10 -0
  66. package/android/src/main/res/drawable/ic_rewind_30.xml +7 -0
  67. package/android/src/main/res/drawable/icon_font_decrease.png +0 -0
  68. package/android/src/main/res/drawable/icon_font_increase.png +0 -0
  69. package/android/src/main/res/drawable/icon_overflow.png +0 -0
  70. package/android/src/main/res/drawable/rbtn_selector.xml +25 -0
  71. package/android/src/main/res/drawable/rbtn_textcolor_selector.xml +16 -0
  72. package/android/src/main/res/drawable/repfr.png +0 -0
  73. package/android/src/main/res/drawable/selector_blue.xml +42 -0
  74. package/android/src/main/res/drawable/selector_green.xml +42 -0
  75. package/android/src/main/res/drawable/selector_purple.xml +42 -0
  76. package/android/src/main/res/drawable/selector_red.xml +42 -0
  77. package/android/src/main/res/drawable/selector_yellow.xml +41 -0
  78. package/android/src/main/res/layout/activity_epub.xml +23 -0
  79. package/android/src/main/res/layout/activity_main.xml +32 -0
  80. package/android/src/main/res/layout/activity_reader.xml +6 -0
  81. package/android/src/main/res/layout/add_catalog_dialog.xml +25 -0
  82. package/android/src/main/res/layout/filter_row.xml +34 -0
  83. package/android/src/main/res/layout/filter_window.xml +24 -0
  84. package/android/src/main/res/layout/fragment_about.xml +150 -0
  85. package/android/src/main/res/layout/fragment_audiobook.xml +151 -0
  86. package/android/src/main/res/layout/fragment_bookshelf.xml +35 -0
  87. package/android/src/main/res/layout/fragment_catalog.xml +56 -0
  88. package/android/src/main/res/layout/fragment_catalog_feed_list.xml +27 -0
  89. package/android/src/main/res/layout/fragment_drm_management.xml +284 -0
  90. package/android/src/main/res/layout/fragment_listview.xml +24 -0
  91. package/android/src/main/res/layout/fragment_outline.xml +31 -0
  92. package/android/src/main/res/layout/fragment_publication_detail.xml +55 -0
  93. package/android/src/main/res/layout/fragment_reader.xml +6 -0
  94. package/android/src/main/res/layout/fragment_screen_reader.xml +143 -0
  95. package/android/src/main/res/layout/fragment_search.xml +39 -0
  96. package/android/src/main/res/layout/item_group_view.xml +22 -0
  97. package/android/src/main/res/layout/item_recycle_book.xml +55 -0
  98. package/android/src/main/res/layout/item_recycle_bookmark.xml +72 -0
  99. package/android/src/main/res/layout/item_recycle_button.xml +23 -0
  100. package/android/src/main/res/layout/item_recycle_catalog.xml +56 -0
  101. package/android/src/main/res/layout/item_recycle_highlight.xml +85 -0
  102. package/android/src/main/res/layout/item_recycle_horizontal.xml +45 -0
  103. package/android/src/main/res/layout/item_recycle_navigation.xml +34 -0
  104. package/android/src/main/res/layout/item_recycle_search.xml +14 -0
  105. package/android/src/main/res/layout/item_spinner_days.xml +19 -0
  106. package/android/src/main/res/layout/my_fragment.xml +13 -0
  107. package/android/src/main/res/layout/popup_delete.xml +29 -0
  108. package/android/src/main/res/layout/popup_note.xml +105 -0
  109. package/android/src/main/res/layout/popup_passphrase.xml +126 -0
  110. package/android/src/main/res/layout/popup_window_user_settings.xml +576 -0
  111. package/android/src/main/res/layout/section_header.xml +25 -0
  112. package/android/src/main/res/layout/view_action_mode.xml +100 -0
  113. package/android/src/main/res/layout/view_action_mode_reverse.xml +99 -0
  114. package/android/src/main/res/menu/bottom_nav_menu.xml +19 -0
  115. package/android/src/main/res/menu/menu_action_mode.xml +26 -0
  116. package/android/src/main/res/menu/menu_bookmark.xml +18 -0
  117. package/android/src/main/res/menu/menu_epub.xml +41 -0
  118. package/android/src/main/res/menu/menu_filter.xml +21 -0
  119. package/android/src/main/res/menu/menu_reader.xml +33 -0
  120. package/android/src/main/res/navigation/navigation.xml +46 -0
  121. package/android/src/main/res/values/arrays.xml +18 -0
  122. package/android/src/main/res/values/colors.xml +22 -0
  123. package/android/src/main/res/values/refs.xml +5 -0
  124. package/android/src/main/res/values/strings.xml +200 -0
  125. package/android/src/main/res/values/styles.xml +46 -0
  126. package/android/src/main/res/xml/network_security_config.xml +38 -0
  127. package/ios/App/AppModule.swift +62 -0
  128. package/ios/Common/Paths.swift +52 -0
  129. package/ios/Common/Publication.swift +15 -0
  130. package/ios/Common/Toolkit/Extensions/AnyPublisher.swift +14 -0
  131. package/ios/Common/Toolkit/Extensions/Future.swift +16 -0
  132. package/ios/Common/Toolkit/Extensions/HTTPClient.swift +65 -0
  133. package/ios/Common/Toolkit/Extensions/Locator.swift +14 -0
  134. package/ios/Common/Toolkit/Extensions/String.swift +16 -0
  135. package/ios/Common/Toolkit/Extensions/UIImage.swift +12 -0
  136. package/ios/Common/Toolkit/Extensions/UIViewController.swift +19 -0
  137. package/ios/Common/Toolkit/ScreenOrientation.swift +13 -0
  138. package/ios/Data/Bookmark.swift +23 -0
  139. package/ios/Reader/Common/ReaderViewController.swift +309 -0
  140. package/ios/Reader/EPUB/AssociatedColors.swift +27 -0
  141. package/ios/Reader/EPUB/EPUBModule.swift +38 -0
  142. package/ios/Reader/EPUB/EPUBViewController.swift +79 -0
  143. package/ios/Reader/ReaderError.swift +25 -0
  144. package/ios/Reader/ReaderFormatModule.swift +27 -0
  145. package/ios/Reader/ReaderModule.swift +84 -0
  146. package/ios/Reader/ReaderService.swift +126 -0
  147. package/ios/Readium-Bridging-Header.h +2 -0
  148. package/ios/Readium.xcodeproj/project.pbxproj +293 -0
  149. package/ios/ReadiumView.swift +156 -0
  150. package/ios/ReadiumViewManager.m +10 -0
  151. package/ios/ReadiumViewManager.swift +9 -0
  152. package/lib/commonjs/enums/Appearance.js +16 -0
  153. package/lib/commonjs/enums/Appearance.js.map +1 -0
  154. package/lib/commonjs/enums/ColumnCount.js +16 -0
  155. package/lib/commonjs/enums/ColumnCount.js.map +1 -0
  156. package/lib/commonjs/enums/FontFamily.js +21 -0
  157. package/lib/commonjs/enums/FontFamily.js.map +1 -0
  158. package/lib/commonjs/enums/TextAlignment.js +15 -0
  159. package/lib/commonjs/enums/TextAlignment.js.map +1 -0
  160. package/lib/commonjs/enums/index.js +58 -0
  161. package/lib/commonjs/enums/index.js.map +1 -0
  162. package/lib/commonjs/index.js +105 -0
  163. package/lib/commonjs/index.js.map +1 -0
  164. package/lib/commonjs/interfaces/Dimensions.js +2 -0
  165. package/lib/commonjs/interfaces/Dimensions.js.map +1 -0
  166. package/lib/commonjs/interfaces/File.js +6 -0
  167. package/lib/commonjs/interfaces/File.js.map +1 -0
  168. package/lib/commonjs/interfaces/Locator.js +2 -0
  169. package/lib/commonjs/interfaces/Locator.js.map +1 -0
  170. package/lib/commonjs/interfaces/Settings.js +71 -0
  171. package/lib/commonjs/interfaces/Settings.js.map +1 -0
  172. package/lib/commonjs/interfaces/index.js +58 -0
  173. package/lib/commonjs/interfaces/index.js.map +1 -0
  174. package/lib/commonjs/utils/index.js +30 -0
  175. package/lib/commonjs/utils/index.js.map +1 -0
  176. package/lib/module/enums/Appearance.js +9 -0
  177. package/lib/module/enums/Appearance.js.map +1 -0
  178. package/lib/module/enums/ColumnCount.js +9 -0
  179. package/lib/module/enums/ColumnCount.js.map +1 -0
  180. package/lib/module/enums/FontFamily.js +14 -0
  181. package/lib/module/enums/FontFamily.js.map +1 -0
  182. package/lib/module/enums/TextAlignment.js +8 -0
  183. package/lib/module/enums/TextAlignment.js.map +1 -0
  184. package/lib/module/enums/index.js +5 -0
  185. package/lib/module/enums/index.js.map +1 -0
  186. package/lib/module/index.js +61 -0
  187. package/lib/module/index.js.map +1 -0
  188. package/lib/module/interfaces/Dimensions.js +2 -0
  189. package/lib/module/interfaces/Dimensions.js.map +1 -0
  190. package/lib/module/interfaces/File.js +2 -0
  191. package/lib/module/interfaces/File.js.map +1 -0
  192. package/lib/module/interfaces/Locator.js +2 -0
  193. package/lib/module/interfaces/Locator.js.map +1 -0
  194. package/lib/module/interfaces/Settings.js +61 -0
  195. package/lib/module/interfaces/Settings.js.map +1 -0
  196. package/lib/module/interfaces/index.js +5 -0
  197. package/lib/module/interfaces/index.js.map +1 -0
  198. package/lib/module/utils/index.js +17 -0
  199. package/lib/module/utils/index.js.map +1 -0
  200. package/lib/typescript/enums/Appearance.d.ts +11 -0
  201. package/lib/typescript/enums/ColumnCount.d.ts +5 -0
  202. package/lib/typescript/enums/FontFamily.d.ts +10 -0
  203. package/lib/typescript/enums/TextAlignment.d.ts +4 -0
  204. package/lib/typescript/enums/index.d.ts +4 -0
  205. package/lib/typescript/index.d.ts +17 -0
  206. package/lib/typescript/interfaces/Dimensions.d.ts +4 -0
  207. package/lib/typescript/interfaces/File.d.ts +11 -0
  208. package/lib/typescript/interfaces/Locator.d.ts +14 -0
  209. package/lib/typescript/interfaces/Settings.d.ts +40 -0
  210. package/lib/typescript/interfaces/index.d.ts +4 -0
  211. package/lib/typescript/utils/index.d.ts +10 -0
  212. package/package.json +160 -0
  213. package/react-native-readium.podspec +25 -0
  214. package/src/enums/Appearance.ts +14 -0
  215. package/src/enums/ColumnCount.ts +6 -0
  216. package/src/enums/FontFamily.ts +11 -0
  217. package/src/enums/TextAlignment.ts +5 -0
  218. package/src/enums/index.ts +4 -0
  219. package/src/index.tsx +78 -0
  220. package/src/interfaces/Dimensions.ts +4 -0
  221. package/src/interfaces/File.ts +14 -0
  222. package/src/interfaces/Locator.ts +14 -0
  223. package/src/interfaces/Settings.ts +85 -0
  224. package/src/interfaces/index.ts +4 -0
  225. 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
+ }