added iOS source code
[wl-app.git] / iOS / Pods / FolioReaderKit / Source / FolioReaderWebView.swift
1 //
2 //  FolioReaderWebView.swift
3 //  FolioReaderKit
4 //
5 //  Created by Hans Seiffert on 21.09.16.
6 //  Copyright (c) 2016 Folio Reader. All rights reserved.
7 //
8
9 import UIKit
10
11 /// The custom WebView used in each page
12 open class FolioReaderWebView: UIWebView {
13     var isColors = false
14     var isShare = false
15     var isOneWord = false
16
17     fileprivate weak var readerContainer: FolioReaderContainer?
18
19     fileprivate var readerConfig: FolioReaderConfig {
20         guard let readerContainer = readerContainer else { return FolioReaderConfig() }
21         return readerContainer.readerConfig
22     }
23
24     fileprivate var book: FRBook {
25         guard let readerContainer = readerContainer else { return FRBook() }
26         return readerContainer.book
27     }
28
29     fileprivate var folioReader: FolioReader {
30         guard let readerContainer = readerContainer else { return FolioReader() }
31         return readerContainer.folioReader
32     }
33
34     override init(frame: CGRect) {
35         fatalError("use init(frame:readerConfig:book:) instead.")
36     }
37
38     init(frame: CGRect, readerContainer: FolioReaderContainer) {
39         self.readerContainer = readerContainer
40
41         super.init(frame: frame)
42     }
43
44     required public init?(coder aDecoder: NSCoder) {
45         fatalError("init(coder:) has not been implemented")
46     }
47
48     // MARK: - UIMenuController
49
50     open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
51         guard readerConfig.useReaderMenuController else {
52             return super.canPerformAction(action, withSender: sender)
53         }
54
55         if isShare {
56             return false
57         } else if isColors {
58             return false
59         } else {
60             if action == #selector(highlight(_:))
61                 || (action == #selector(define(_:)) && isOneWord)
62                 || (action == #selector(play(_:)) && (book.hasAudio || readerConfig.enableTTS))
63                 || (action == #selector(share(_:)) && readerConfig.allowSharing)
64                 || (action == #selector(copy(_:)) && readerConfig.allowSharing) {
65                 return true
66             }
67             return false
68         }
69     }
70
71     // MARK: - UIMenuController - Actions
72
73     @objc func share(_ sender: UIMenuController) {
74         let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
75
76         let shareImage = UIAlertAction(title: self.readerConfig.localizedShareImageQuote, style: .default, handler: { (action) -> Void in
77             if self.isShare {
78                 if let textToShare = self.js("getHighlightContent()") {
79                     self.folioReader.readerCenter?.presentQuoteShare(textToShare)
80                 }
81             } else {
82                 if let textToShare = self.js("getSelectedText()") {
83                     self.folioReader.readerCenter?.presentQuoteShare(textToShare)
84
85                     self.clearTextSelection()
86                 }
87             }
88             self.setMenuVisible(false)
89         })
90
91         let shareText = UIAlertAction(title: self.readerConfig.localizedShareTextQuote, style: .default) { (action) -> Void in
92             if self.isShare {
93                 if let textToShare = self.js("getHighlightContent()") {
94                     self.folioReader.readerCenter?.shareHighlight(textToShare, rect: sender.menuFrame)
95                 }
96             } else {
97                 if let textToShare = self.js("getSelectedText()") {
98                     self.folioReader.readerCenter?.shareHighlight(textToShare, rect: sender.menuFrame)
99                 }
100             }
101             self.setMenuVisible(false)
102         }
103
104         let cancel = UIAlertAction(title: self.readerConfig.localizedCancel, style: .cancel, handler: nil)
105
106         alertController.addAction(shareImage)
107         alertController.addAction(shareText)
108         alertController.addAction(cancel)
109
110         if let alert = alertController.popoverPresentationController {
111             alert.sourceView = self.folioReader.readerCenter?.currentPage
112             alert.sourceRect = sender.menuFrame
113         }
114
115         self.folioReader.readerCenter?.present(alertController, animated: true, completion: nil)
116     }
117
118     func colors(_ sender: UIMenuController?) {
119         isColors = true
120         createMenu(options: false)
121         setMenuVisible(true)
122     }
123
124     func remove(_ sender: UIMenuController?) {
125         if let removedId = js("removeThisHighlight()") {
126             Highlight.removeById(withConfiguration: self.readerConfig, highlightId: removedId)
127         }
128         setMenuVisible(false)
129     }
130
131     @objc func highlight(_ sender: UIMenuController?) {
132         let highlightAndReturn = js("highlightString('\(HighlightStyle.classForStyle(self.folioReader.currentHighlightStyle))')")
133         let jsonData = highlightAndReturn?.data(using: String.Encoding.utf8)
134
135         do {
136             let json = try JSONSerialization.jsonObject(with: jsonData!, options: []) as! NSArray
137             let dic = json.firstObject as! [String: String]
138             let rect = CGRectFromString(dic["rect"]!)
139             guard let startOffset = dic["startOffset"] else {
140                 return
141             }
142             guard let endOffset = dic["endOffset"] else {
143                 return
144             }
145
146             createMenu(options: true)
147             setMenuVisible(true, andRect: rect)
148
149             // Persist
150             guard
151                 let html = js("getHTML()"),
152                 let identifier = dic["id"],
153                 let bookId = (self.book.name as NSString?)?.deletingPathExtension else {
154                     return
155             }
156
157             let pageNumber = folioReader.readerCenter?.currentPageNumber ?? 0
158             let match = Highlight.MatchingHighlight(text: html, id: identifier, startOffset: startOffset, endOffset: endOffset, bookId: bookId, currentPage: pageNumber)
159             let highlight = Highlight.matchHighlight(match)
160             highlight?.persist(withConfiguration: self.readerConfig)
161
162         } catch {
163             print("Could not receive JSON")
164         }
165     }
166
167     @objc func define(_ sender: UIMenuController?) {
168         guard let selectedText = js("getSelectedText()") else {
169             return
170         }
171
172         self.setMenuVisible(false)
173         self.clearTextSelection()
174
175         let vc = UIReferenceLibraryViewController(term: selectedText)
176         vc.view.tintColor = self.readerConfig.tintColor
177         guard let readerContainer = readerContainer else { return }
178         readerContainer.show(vc, sender: nil)
179     }
180
181     @objc func play(_ sender: UIMenuController?) {
182         self.folioReader.readerAudioPlayer?.play()
183
184         self.clearTextSelection()
185     }
186
187     func setYellow(_ sender: UIMenuController?) {
188         changeHighlightStyle(sender, style: .yellow)
189     }
190
191     func setGreen(_ sender: UIMenuController?) {
192         changeHighlightStyle(sender, style: .green)
193     }
194
195     func setBlue(_ sender: UIMenuController?) {
196         changeHighlightStyle(sender, style: .blue)
197     }
198
199     func setPink(_ sender: UIMenuController?) {
200         changeHighlightStyle(sender, style: .pink)
201     }
202
203     func setUnderline(_ sender: UIMenuController?) {
204         changeHighlightStyle(sender, style: .underline)
205     }
206
207     func changeHighlightStyle(_ sender: UIMenuController?, style: HighlightStyle) {
208         self.folioReader.currentHighlightStyle = style.rawValue
209
210         if let updateId = js("setHighlightStyle('\(HighlightStyle.classForStyle(style.rawValue))')") {
211             Highlight.updateById(withConfiguration: self.readerConfig, highlightId: updateId, type: style)
212         }
213     }
214
215     // MARK: - Create and show menu
216
217     func createMenu(options: Bool) {
218         guard (self.readerConfig.useReaderMenuController == true) else {
219             return
220         }
221
222         isShare = options
223
224         let colors = UIImage(readerImageNamed: "colors-marker")
225         let share = UIImage(readerImageNamed: "share-marker")
226         let remove = UIImage(readerImageNamed: "no-marker")
227         let yellow = UIImage(readerImageNamed: "yellow-marker")
228         let green = UIImage(readerImageNamed: "green-marker")
229         let blue = UIImage(readerImageNamed: "blue-marker")
230         let pink = UIImage(readerImageNamed: "pink-marker")
231         let underline = UIImage(readerImageNamed: "underline-marker")
232
233         let menuController = UIMenuController.shared
234
235         let highlightItem = UIMenuItem(title: self.readerConfig.localizedHighlightMenu, action: #selector(highlight(_:)))
236         let playAudioItem = UIMenuItem(title: self.readerConfig.localizedPlayMenu, action: #selector(play(_:)))
237         let defineItem = UIMenuItem(title: self.readerConfig.localizedDefineMenu, action: #selector(define(_:)))
238         let colorsItem = UIMenuItem(title: "C", image: colors) { [weak self] _ in
239             self?.colors(menuController)
240         }
241         let shareItem = UIMenuItem(title: "S", image: share) { [weak self] _ in
242             self?.share(menuController)
243         }
244         let removeItem = UIMenuItem(title: "R", image: remove) { [weak self] _ in
245             self?.remove(menuController)
246         }
247         let yellowItem = UIMenuItem(title: "Y", image: yellow) { [weak self] _ in
248             self?.setYellow(menuController)
249         }
250         let greenItem = UIMenuItem(title: "G", image: green) { [weak self] _ in
251             self?.setGreen(menuController)
252         }
253         let blueItem = UIMenuItem(title: "B", image: blue) { [weak self] _ in
254             self?.setBlue(menuController)
255         }
256         let pinkItem = UIMenuItem(title: "P", image: pink) { [weak self] _ in
257             self?.setPink(menuController)
258         }
259         let underlineItem = UIMenuItem(title: "U", image: underline) { [weak self] _ in
260             self?.setUnderline(menuController)
261         }
262
263         var menuItems = [shareItem]
264
265         // menu on existing highlight
266         if isShare {
267             menuItems = [colorsItem, removeItem]
268             if (self.readerConfig.allowSharing == true) {
269                 menuItems.append(shareItem)
270             }
271         } else if isColors {
272             // menu for selecting highlight color
273             menuItems = [yellowItem, greenItem, blueItem, pinkItem, underlineItem]
274         } else {
275             // default menu
276             // PD: changed
277             menuItems = [highlightItem/*, defineItem*/, shareItem]
278
279             if self.book.hasAudio || self.readerConfig.enableTTS {
280                 menuItems.insert(playAudioItem, at: 0)
281             }
282
283             if (self.readerConfig.allowSharing == false) {
284                 menuItems.removeLast()
285             }
286         }
287         
288         menuController.menuItems = menuItems
289     }
290     
291     open func setMenuVisible(_ menuVisible: Bool, animated: Bool = true, andRect rect: CGRect = CGRect.zero) {
292         if !menuVisible && isShare || !menuVisible && isColors {
293             isColors = false
294             isShare = false
295         }
296         
297         if menuVisible  {
298             if !rect.equalTo(CGRect.zero) {
299                 UIMenuController.shared.setTargetRect(rect, in: self)
300             }
301         }
302         
303         UIMenuController.shared.setMenuVisible(menuVisible, animated: animated)
304     }
305     
306     // MARK: - Java Script Bridge
307     
308     @discardableResult open func js(_ script: String) -> String? {
309         let callback = self.stringByEvaluatingJavaScript(from: script)
310         if callback!.isEmpty { return nil }
311         return callback
312     }
313     
314     // MARK: WebView
315     
316     func clearTextSelection() {
317         // Forces text selection clearing
318         // @NOTE: this doesn't seem to always work
319         
320         self.isUserInteractionEnabled = false
321         self.isUserInteractionEnabled = true
322     }
323     
324     func setupScrollDirection() {
325         switch self.readerConfig.scrollDirection {
326         case .vertical, .defaultVertical, .horizontalWithVerticalContent:
327             scrollView.isPagingEnabled = false
328             paginationMode = .unpaginated
329             scrollView.bounces = true
330             break
331         case .horizontal:
332             scrollView.isPagingEnabled = true
333             paginationMode = .leftToRight
334             paginationBreakingMode = .page
335             scrollView.bounces = false
336             break
337         }
338     }
339 }