added iOS source code
[wl-app.git] / iOS / WolneLektury / Screens / BookDetails / BookDetailsViewController.swift
1 //
2 //  BookDetailsViewController.swift
3 //  WolneLektury
4 //
5 //  Created by Pawel Dabrowski on 19/06/2018.
6 //  Copyright © 2018 Fundacja Nowoczesna Polska. All rights reserved.
7 //
8
9 import UIKit
10 import MZDownloadManager
11 import FolioReaderKit
12 import MatomoTracker
13
14 class BookDetailsViewController: WLViewController {
15     
16     var bookDetailsModel: BookDetailsModel?
17     private var bookSlug: String!
18     private var isFavourite = false
19     private var isBookPremium: Bool = false
20     @IBOutlet weak var headerView: UIView!
21     @IBOutlet weak var refreshButton: ActivityIndicatorButton!
22     @IBOutlet weak var backButton: UIButton!
23     @IBOutlet weak var tableView: UITableView!
24     
25     @IBOutlet weak var favouriteButton: UIButton!
26     @IBOutlet weak var shareButton: UIButton!
27     @IBOutlet weak var buttonsContainer: UIView!
28     @IBOutlet weak var headerHeightConstraint: NSLayoutConstraint!
29     @IBOutlet weak var buttonsContainerWidthConstraint: NSLayoutConstraint!
30
31     @IBOutlet weak var becomeFriendView: UIView!
32     @IBOutlet weak var becomeFriendLabel: UILabel!
33     @IBOutlet weak var becomeFriendButton: UIButton!
34     @IBOutlet weak var becomeFriendStarImageView: UIImageView!
35     @IBOutlet weak var headerLabel: UILabel!
36     
37     var topColor = UIColor(red:0.91, green:0.31, blue:0.20, alpha:1.00)
38     var headerProgress: CGFloat = 100
39
40     var cellsArray = [WLTableViewCell]()
41     var readCell: BookDetailsButtonTableViewCell?
42     var audiobookCell: BookDetailsButtonTableViewCell?
43     var readingState: ReadingStateModel.ReadingState = .unknown
44     
45     @IBAction func refreshButtonAction(_ sender: Any) {
46         refreshData()
47     }
48     
49     @IBAction func becomeFriendButtonAction(_ sender: Any) {
50         onBecomeFriendButtonTapped()
51     }
52     
53     @IBAction func backButtonAction(_ sender: Any) {
54         navigationController?.popViewController(animated: true)
55     }
56     
57     @IBAction func favouriteButtonAction(_ sender: Any) {
58         setFavourite(favourite: !isFavourite)
59     }
60
61     @IBAction func sharebuttonAction(_ sender: UIButton) {
62         
63         if let url = bookDetailsModel?.url {
64             self.share(string: url, button: sender)
65         }
66     }
67     
68     static func instance(bookSlug: String, isBookPremium: Bool = false) -> BookDetailsViewController{
69         let controller = BookDetailsViewController.instance()
70         controller.bookSlug = bookSlug
71         controller.isBookPremium = isBookPremium
72         return controller
73     }
74
75     var bigHeaderHeight: CGFloat = 200
76     var smallHeaderHeight: CGFloat = 200
77     
78     open override var preferredStatusBarStyle : UIStatusBarStyle {
79         return .lightContent
80     }
81
82     override func viewDidLoad() {
83         super.viewDidLoad()
84         
85         headerLabel.text = ""
86         tableView.isHidden = true
87         buttonsContainer.isHidden = true
88         becomeFriendStarImageView.tintColor = UIColor.white
89         becomeFriendButton.layer.cornerRadius = 18
90         refreshButton.tintColor = UIColor.white
91
92         bigHeaderHeight = 190 + UIApplication.shared.statusBarFrame.size.height
93         smallHeaderHeight = 44 + UIApplication.shared.statusBarFrame.size.height
94         headerHeightConstraint.constant = bigHeaderHeight
95         
96         headerView.backgroundColor = Constants.Colors.navbarBgColor()
97         tableView.rowHeight = UITableViewAutomaticDimension
98         tableView.estimatedRowHeight = 200
99         tableView.separatorStyle = .none
100         tableView.registerNib(name: "BookDetailsHeaderTableViewCell")
101         tableView.registerNib(name: "BookDetailsInfoTableViewCell")
102         refreshData()
103         
104         if syncManager.isLoggedIn() == false{
105             favouriteButton.isHidden = true
106             buttonsContainerWidthConstraint.constant = 42
107         }
108         
109         let tintColor = UIColor(red:0.91, green:0.31, blue:0.20, alpha:1.00)
110         favouriteButton.tintColor = tintColor
111         shareButton.tintColor = tintColor
112         favouriteButton.layer.cornerRadius = 21
113         shareButton.layer.cornerRadius = 21
114         let buttonBorderColor = UIColor(red:0.96, green:0.96, blue:0.96, alpha:1.00)
115         favouriteButton.layer.borderColor = buttonBorderColor.cgColor
116         shareButton.layer.borderColor = buttonBorderColor.cgColor
117         favouriteButton.layer.borderWidth = 1.0
118         shareButton.layer.borderWidth = 1.0
119
120         edgesForExtendedLayout = []
121         extendedLayoutIncludesOpaqueBars = false
122         if #available(iOS 11.0, *) {
123             tableView.contentInsetAdjustmentBehavior = .never
124         } else {
125             automaticallyAdjustsScrollViewInsets = false
126         }
127         
128         if isBookPremium && DatabaseManager.shared.isUserPremium() == false {
129             becomeFriendView.isHidden = false
130             becomeFriendLabel.text = "become_friend_desc".localized
131             becomeFriendButton.text = "become_friend_button".localized.uppercased()
132         }
133         else {
134             becomeFriendView.isHidden = true
135         }
136         
137         refreshIsFavourite()
138         refreshReadingState()
139     }
140     
141     func createCells(bookDetails: BookDetailsModel) {
142         cellsArray = [WLTableViewCell]()
143         self.bookDetailsModel = bookDetails
144         let titleCell = BookDetailsHeaderTableViewCell.instance(height: bigHeaderHeight)
145         titleCell.setup(bookModel: bookDetails, topColor: topColor)
146         cellsArray.append(titleCell)
147         let infoCell = BookDetailsInfoTableViewCell.instance()
148         infoCell.setup(bookModel: bookDetails)
149         cellsArray.append(infoCell)
150         cellsArray.append(BookDetailsSeparatorTableViewCell.instance())
151         
152         if bookDetails.fragmentHtml.count > 0{
153             let fragmentCell = BookDetailsFragmentTableViewCell.instance()
154             fragmentCell.setup(fragmentTitle: bookDetails.fragmentTitle, fragmentHtml: bookDetails.fragmentHtml)
155             cellsArray.append( fragmentCell)
156             cellsArray.append(BookDetailsSeparatorTableViewCell.instance())
157
158         }
159         
160         if isBookPremium && DatabaseManager.shared.isUserPremium() == false{ // dont show buttons when user is not premium
161             
162         }
163         else{
164             
165             if bookDetails.epub.count > 0{
166                 var buttonType = BookDetailsButtonType.download_ebook
167                 switch DownloadManager.sharedInstance.checkEbookStatus(bookSlug: bookDetails.slug){
168                 case .downloaded:
169                     buttonType = .download_ebook_read
170                 case .downloading:
171                     DownloadManager.sharedInstance.delegate = self
172                     buttonType = .download_ebook_loading
173                 default:
174                     break
175                 }
176                 readCell = BookDetailsButtonTableViewCell.instance(delegate: self, bookDetailsButtonType: buttonType, bookDetailsModel: bookDetails)
177                 cellsArray.append(readCell!)
178             }
179             
180             if bookDetails.getAudiobookFilesUrls().count > 0 {
181                 var buttonType = BookDetailsButtonType.download_audiobook
182                 
183                 switch DownloadManager.sharedInstance.checkAudiobookStatus(bookDetailsModel: bookDetails){
184                 case .downloaded:
185                     buttonType = .download_audiobook_listen
186                 case .downloading:
187                     DownloadManager.sharedInstance.delegate = self
188                     buttonType = .download_audiobook_loading
189                 default:
190                     break
191                 }
192                 audiobookCell = BookDetailsButtonTableViewCell.instance(delegate: self, bookDetailsButtonType: buttonType, bookDetailsModel: bookDetails)
193                 cellsArray.append(audiobookCell!)
194             }
195             
196 //            cellsArray.append(BookDetailsButtonTableViewCell.instance(delegate: self, bookDetailsButtonType: .support_us, bookDetailsModel: bookDetails))
197         }
198     }
199     
200     func bookDetailsDownloaded(bookDetails: BookDetailsModel){
201         self.bookDetailsModel = bookDetails
202         tableView.isHidden = false
203         buttonsContainer.isHidden = false
204         headerView.alpha = 0
205         topColor = self.bookDetailsModel!.bgColor
206         headerLabel.text = bookDetails.title
207         createCells(bookDetails: self.bookDetailsModel!)
208         tableView.reloadData()
209     }
210     
211     func setFavourite(favourite: Bool){
212         isFavourite = favourite
213         refreshFavouriteButton()
214         
215         syncManager.setFavouriteState(slug: bookSlug, favourite: favourite) {[weak self] (result) in
216             
217             guard let strongSelf = self else{
218                 return
219             }
220             
221             strongSelf.refreshButton.setIndicatorButtonState(state: .hidden)
222             switch result {
223             case .success/*(let model)*/:
224                 strongSelf.isFavourite = favourite
225             case .failure/*(let error)*/:
226                 
227                 break
228             }
229         }
230     }
231     
232     func refreshFavouriteButton(){
233         favouriteButton.setImage(isFavourite ? #imageLiteral(resourceName: "icon_heart-fill-big") : #imageLiteral(resourceName: "icon_heart-outline-big"), for: .normal)
234     }
235     
236     func refreshIsFavourite(){
237         guard syncManager.isLoggedIn() else { return }
238         
239         syncManager.getFavouriteState(slug: bookSlug) {[weak self] (result) in
240             
241             guard let strongSelf = self else{
242                 return
243             }
244             
245             switch result {
246             case .success(let model):
247                 strongSelf.isFavourite = (model as! LikeModel).likes
248                 strongSelf.refreshFavouriteButton()
249             case .failure/*(let error)*/:
250                 break
251             }
252         }
253
254     }
255     
256     func refreshReadingState(){
257         
258         guard syncManager.isLoggedIn() else {
259             readingState = .unknown
260             return
261         }
262         
263         syncManager.getReadingState(slug: bookSlug, completionHandler: { [weak self] (result) in
264         
265             guard let strongSelf = self else{
266                 return
267             }
268             
269             switch result {
270             case .success(let model):
271                 strongSelf.readingState = (model as! ReadingStateModel).state
272             case .failure/*(let error)*/:
273                 break
274             }
275         })
276     }
277
278     
279     func refreshData(){
280         var storedBook = false
281         if let downloadedModel = DatabaseManager.shared.getBookFromDownloaded(bookSlug: bookSlug) {
282             storedBook = true
283             bookDetailsDownloaded(bookDetails: downloadedModel)
284         }
285
286         if storedBook == false {
287             refreshButton.setIndicatorButtonState(state: .loading)
288         }
289         
290         syncManager.getBookDetails(bookSlug: bookSlug) {[weak self] (result) in
291             
292             guard let strongSelf = self else{
293                 return
294             }
295             
296             strongSelf.refreshButton.setIndicatorButtonState(state: .hidden)
297             switch result {
298             case .success(let model):
299                 if let model = model as? BookDetailsModel{
300                     model.slug = strongSelf.bookSlug
301                     if storedBook {
302                         DatabaseManager.shared.addBookToDownloaded(bookDetailsModel: model)
303                     }
304                     strongSelf.bookDetailsDownloaded(bookDetails: model)
305                 }
306                 
307             case .failure/*(let error)*/:
308                 if storedBook == false {
309                     strongSelf.refreshButton.setIndicatorButtonState(state: .button)
310                     self?.view.makeToast("book_loading_error".localized, duration: 3.0, position: .bottom)
311                 }
312             }
313         }
314     }
315     
316     override func viewWillAppear(_ animated: Bool) {
317         super.viewWillAppear(animated)
318         openedPlayer = false
319         navigationController?.setNavigationBarHidden(true, animated: true)
320         DownloadManager.sharedInstance.delegate = self
321     }
322     
323     override func viewWillDisappear(_ animated: Bool) {
324         super.viewWillDisappear(animated)
325         if !openedPlayer {
326             navigationController?.setNavigationBarHidden(false, animated: true)
327         }
328         DownloadManager.sharedInstance.delegate = nil
329     }
330     
331     func downloadEbook(){
332         guard let bookDetailsModel = bookDetailsModel, bookDetailsModel.epub.count > 0 else {return}
333         
334         DatabaseManager.shared.addBookToDownloaded(bookDetailsModel: bookDetailsModel)
335         DownloadManager.sharedInstance.delegate = self
336         DownloadManager.sharedInstance.downloadEbook(bookDetailsModel: bookDetailsModel)
337     }
338     
339     var openedPlayer = false
340     func downloadAudiobooks(){
341         guard let bookDetailsModel = bookDetailsModel, bookDetailsModel.getAudiobookFilesUrls().count > 0 else {return}
342         
343         DatabaseManager.shared.addBookToDownloaded(bookDetailsModel: bookDetailsModel)
344
345         DownloadManager.sharedInstance.delegate = self
346         DownloadManager.sharedInstance.downloadAudiobooks(bookDetailsModel: bookDetailsModel)
347     }
348
349     func updateReadingStateIfNeeded(state: ReadingStateModel.ReadingState) {
350         if state == .reading && readingState != .not_started {
351             return
352         }
353         
354         if state == .complete && readingState == .complete {
355             return
356         }
357
358         syncManager.setReadingState(slug: bookSlug, readingState: state, completionHandler: nil)
359     }
360     
361     func openFolioReader() {
362         guard ebookExists(bookSlug: bookSlug) else {
363             return
364         }
365
366         updateReadingStateIfNeeded(state: .reading)
367         
368         var array = parentNames()
369         array.append("Reader")
370         MatomoTracker.shared.track(view: array)
371
372         let config = WLReaderConfig()
373         let bookPath = FileType.ebook.pathForFileName(filename: bookSlug, bookSlug: bookSlug)
374         let bookDirectory = FileType.ebook.destinationPathWithSlug(bookSlug: bookSlug) + "unzipped/"
375
376         if !FileManager.default.fileExists(atPath: bookDirectory) {
377             try! FileManager.default.createDirectory(atPath: bookDirectory, withIntermediateDirectories: true, attributes: nil)
378         }
379
380         let folioReader = FolioReader()
381         folioReader.delegate = self
382         
383         folioReader.presentReader(parentViewController: self.navigationController!, withEpubPath: bookPath, unzipPath: bookDirectory, andConfig: config, shouldRemoveEpub: false)
384     }
385     
386     func openAudiobook(afterDownload: Bool) {
387         guard let bookDetailsModel = bookDetailsModel else { return }
388         
389         updateReadingStateIfNeeded(state: .reading)
390
391         openedPlayer = true
392         navigationController?.pushViewController(PlayerViewController.instance(bookDetailsModel: bookDetailsModel), animated: true)
393     }
394     
395     deinit {
396     }
397 }
398
399 extension BookDetailsViewController: UITableViewDataSource{
400     
401     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
402         return cellsArray.count
403     }
404     
405     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
406         return cellsArray[indexPath.row]
407     }
408 }
409
410 extension BookDetailsViewController: UITableViewDelegate{
411     func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
412         return cellsArray[indexPath.row].getHeight()
413     }
414     
415     func scrollViewDidScroll(_ scrollView: UIScrollView) {
416         print(scrollView.contentOffset)
417         
418         var progress: CGFloat = 100
419
420         var newValue = bigHeaderHeight - scrollView.contentOffset.y
421         if newValue < smallHeaderHeight{
422             newValue = smallHeaderHeight
423             progress = 0
424         }
425         else{
426             progress = (bigHeaderHeight - smallHeaderHeight - scrollView.contentOffset.y) / (bigHeaderHeight - smallHeaderHeight)
427             if progress > 100{
428                 progress = 100
429             }
430         }
431         
432         headerView.alpha = 1 - progress
433         buttonsContainer.alpha = progress
434         headerHeightConstraint.constant = newValue
435         headerProgress = progress
436     }
437     
438     func showHeader(){
439         headerView.alpha = 1
440         buttonsContainer.alpha = 0
441         headerHeightConstraint.constant = smallHeaderHeight
442         headerProgress = 0
443     }
444 }
445
446 extension BookDetailsViewController: BookDetailsButtonTableViewCellDelegate{
447     
448     func bookDetailsButtonTableViewCellButtonTapped(buttonType: BookDetailsButtonType){
449         switch buttonType {
450         case .download_ebook:
451             downloadEbook()
452         case .download_ebook_read:
453             openFolioReader()
454         case .download_audiobook:
455             downloadAudiobooks()
456         case .download_audiobook_listen:
457             openAudiobook(afterDownload: false)
458         default:
459             break
460         }
461     }
462     
463     func bookDetailsButtonTableViewCellDeleteButtonTapped(buttonType: BookDetailsButtonType){
464         switch buttonType {
465         case .download_ebook_read, .download_ebook_loading:
466             DownloadManager.sharedInstance.deleteEbook(bookSlug: bookSlug)
467             readCell?.setup(bookDetailsButtonType: .download_ebook, progress: nil, bookDetailsModel: bookDetailsModel)
468             bookDetailsModel = (bookDetailsModel?.copy() as! BookDetailsModel)
469             let _ = DatabaseManager.shared.removeBookFromDownloaded(bookSlug: bookSlug)
470         case .download_audiobook_listen, .download_audiobook_loading:
471             if let bookDetails = PlayerController.shared.currentBookDetails, bookDetails.slug == bookSlug{
472                 PlayerController.shared.stopAndClear()
473             }
474             DownloadManager.sharedInstance.clearDownloadingAudiobookFromQueue(bookSlug: bookSlug)
475             DownloadManager.sharedInstance.deleteAudiobook(bookSlug: bookSlug)
476             audiobookCell?.setup(bookDetailsButtonType: .download_audiobook, progress:nil, bookDetailsModel: bookDetailsModel)
477             bookDetailsModel = (bookDetailsModel?.copy() as! BookDetailsModel)
478             let _ = DatabaseManager.shared.removeBookFromDownloaded(bookSlug: bookSlug)
479         default:
480             break
481         }
482     }
483 }
484
485 extension BookDetailsViewController: DownloadManagerDelegate{
486     
487     func downloadManagerDownloadProgressChanged(model: MZDownloadModel, allProgress: Float, bookSlug: String) {
488         guard let bookDetailsModel = bookDetailsModel, bookDetailsModel.slug == bookSlug else { return        }
489
490         if model.isAudiobook() {
491             audiobookCell?.setup(bookDetailsButtonType: .download_audiobook_loading, progress: allProgress,  bookDetailsModel: bookDetailsModel)
492         }
493         else if model.isEbook() {
494             readCell?.setup(bookDetailsButtonType: .download_ebook_loading, progress: model.progress,  bookDetailsModel: bookDetailsModel)
495         }
496     }
497     
498     func downloadManagerDownloadFinished(model: MZDownloadModel, bookSlug: String) {
499         guard let bookDetailsModel = bookDetailsModel, bookDetailsModel.slug == bookSlug else { return        }
500         
501         if model.isAudiobook() {
502             
503             bookDetailsModel.setInitialAudiobookChaptersValuesIfNeeded()
504             
505             audiobookCell?.setup(bookDetailsButtonType: .download_audiobook_listen, progress: nil,  bookDetailsModel: bookDetailsModel)
506             openAudiobook(afterDownload: true)
507         }
508         else if model.isEbook() {
509             readCell?.setup(bookDetailsButtonType: .download_ebook_read, progress: nil,  bookDetailsModel: bookDetailsModel)
510             openFolioReader()
511         }
512     }
513     
514     func downloadManagerDownloadFailed(model: MZDownloadModel, bookSlug: String) {
515         guard let bookDetailsModel = bookDetailsModel, bookDetailsModel.slug == bookSlug else { return }
516
517         if model.isAudiobook() {
518             view.makeToast("audiobook_download_error".localized, duration: 3.0, position: .bottom)
519             audiobookCell?.setup(bookDetailsButtonType: .download_audiobook, progress: nil, bookDetailsModel: bookDetailsModel)
520         }
521         else if model.isEbook() {
522             view.makeToast("book_download_error".localized, duration: 3.0, position: .bottom)
523             readCell?.setup(bookDetailsButtonType: .download_ebook, progress: nil, bookDetailsModel: bookDetailsModel)
524         }
525     }
526 }
527
528 extension BookDetailsViewController: FolioReaderDelegate{
529     @objc func folioReaderDidClose(_ folioReader: FolioReader) {
530         guard let bookDetailsModel = bookDetailsModel, let progressValues = folioReader.getProgressValues(), progressValues.currentPage > 0, progressValues.currentPage <= progressValues.totalPages else { return }
531         
532         DatabaseManager.shared.updateCurrentChapters(bookDetailsModel: bookDetailsModel, currentChapter: progressValues.currentPage, totalChapters: progressValues.totalPages, currentAudioChapter: nil, totalAudioChapters: nil)
533         readCell?.setup(bookDetailsButtonType: .download_ebook_read, progress: nil, bookDetailsModel: bookDetailsModel)
534         
535         if progressValues.currentPage == progressValues.totalPages && readingState == ReadingStateModel.ReadingState.reading{
536            updateReadingStateIfNeeded(state: .complete)
537         }
538     }
539 }