added iOS source code
[wl-app.git] / iOS / WolneLektury / Screens / BookDetails / BookDetailsViewController.swift
diff --git a/iOS/WolneLektury/Screens/BookDetails/BookDetailsViewController.swift b/iOS/WolneLektury/Screens/BookDetails/BookDetailsViewController.swift
new file mode 100644 (file)
index 0000000..3c8685f
--- /dev/null
@@ -0,0 +1,539 @@
+//
+//  BookDetailsViewController.swift
+//  WolneLektury
+//
+//  Created by Pawel Dabrowski on 19/06/2018.
+//  Copyright © 2018 Fundacja Nowoczesna Polska. All rights reserved.
+//
+
+import UIKit
+import MZDownloadManager
+import FolioReaderKit
+import MatomoTracker
+
+class BookDetailsViewController: WLViewController {
+    
+    var bookDetailsModel: BookDetailsModel?
+    private var bookSlug: String!
+    private var isFavourite = false
+    private var isBookPremium: Bool = false
+    @IBOutlet weak var headerView: UIView!
+    @IBOutlet weak var refreshButton: ActivityIndicatorButton!
+    @IBOutlet weak var backButton: UIButton!
+    @IBOutlet weak var tableView: UITableView!
+    
+    @IBOutlet weak var favouriteButton: UIButton!
+    @IBOutlet weak var shareButton: UIButton!
+    @IBOutlet weak var buttonsContainer: UIView!
+    @IBOutlet weak var headerHeightConstraint: NSLayoutConstraint!
+    @IBOutlet weak var buttonsContainerWidthConstraint: NSLayoutConstraint!
+
+    @IBOutlet weak var becomeFriendView: UIView!
+    @IBOutlet weak var becomeFriendLabel: UILabel!
+    @IBOutlet weak var becomeFriendButton: UIButton!
+    @IBOutlet weak var becomeFriendStarImageView: UIImageView!
+    @IBOutlet weak var headerLabel: UILabel!
+    
+    var topColor = UIColor(red:0.91, green:0.31, blue:0.20, alpha:1.00)
+    var headerProgress: CGFloat = 100
+
+    var cellsArray = [WLTableViewCell]()
+    var readCell: BookDetailsButtonTableViewCell?
+    var audiobookCell: BookDetailsButtonTableViewCell?
+    var readingState: ReadingStateModel.ReadingState = .unknown
+    
+    @IBAction func refreshButtonAction(_ sender: Any) {
+        refreshData()
+    }
+    
+    @IBAction func becomeFriendButtonAction(_ sender: Any) {
+        onBecomeFriendButtonTapped()
+    }
+    
+    @IBAction func backButtonAction(_ sender: Any) {
+        navigationController?.popViewController(animated: true)
+    }
+    
+    @IBAction func favouriteButtonAction(_ sender: Any) {
+        setFavourite(favourite: !isFavourite)
+    }
+
+    @IBAction func sharebuttonAction(_ sender: UIButton) {
+        
+        if let url = bookDetailsModel?.url {
+            self.share(string: url, button: sender)
+        }
+    }
+    
+    static func instance(bookSlug: String, isBookPremium: Bool = false) -> BookDetailsViewController{
+        let controller = BookDetailsViewController.instance()
+        controller.bookSlug = bookSlug
+        controller.isBookPremium = isBookPremium
+        return controller
+    }
+
+    var bigHeaderHeight: CGFloat = 200
+    var smallHeaderHeight: CGFloat = 200
+    
+    open override var preferredStatusBarStyle : UIStatusBarStyle {
+        return .lightContent
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+        headerLabel.text = ""
+        tableView.isHidden = true
+        buttonsContainer.isHidden = true
+        becomeFriendStarImageView.tintColor = UIColor.white
+        becomeFriendButton.layer.cornerRadius = 18
+        refreshButton.tintColor = UIColor.white
+
+        bigHeaderHeight = 190 + UIApplication.shared.statusBarFrame.size.height
+        smallHeaderHeight = 44 + UIApplication.shared.statusBarFrame.size.height
+        headerHeightConstraint.constant = bigHeaderHeight
+        
+        headerView.backgroundColor = Constants.Colors.navbarBgColor()
+        tableView.rowHeight = UITableViewAutomaticDimension
+        tableView.estimatedRowHeight = 200
+        tableView.separatorStyle = .none
+        tableView.registerNib(name: "BookDetailsHeaderTableViewCell")
+        tableView.registerNib(name: "BookDetailsInfoTableViewCell")
+        refreshData()
+        
+        if syncManager.isLoggedIn() == false{
+            favouriteButton.isHidden = true
+            buttonsContainerWidthConstraint.constant = 42
+        }
+        
+        let tintColor = UIColor(red:0.91, green:0.31, blue:0.20, alpha:1.00)
+        favouriteButton.tintColor = tintColor
+        shareButton.tintColor = tintColor
+        favouriteButton.layer.cornerRadius = 21
+        shareButton.layer.cornerRadius = 21
+        let buttonBorderColor = UIColor(red:0.96, green:0.96, blue:0.96, alpha:1.00)
+        favouriteButton.layer.borderColor = buttonBorderColor.cgColor
+        shareButton.layer.borderColor = buttonBorderColor.cgColor
+        favouriteButton.layer.borderWidth = 1.0
+        shareButton.layer.borderWidth = 1.0
+
+        edgesForExtendedLayout = []
+        extendedLayoutIncludesOpaqueBars = false
+        if #available(iOS 11.0, *) {
+            tableView.contentInsetAdjustmentBehavior = .never
+        } else {
+            automaticallyAdjustsScrollViewInsets = false
+        }
+        
+        if isBookPremium && DatabaseManager.shared.isUserPremium() == false {
+            becomeFriendView.isHidden = false
+            becomeFriendLabel.text = "become_friend_desc".localized
+            becomeFriendButton.text = "become_friend_button".localized.uppercased()
+        }
+        else {
+            becomeFriendView.isHidden = true
+        }
+        
+        refreshIsFavourite()
+        refreshReadingState()
+    }
+    
+    func createCells(bookDetails: BookDetailsModel) {
+        cellsArray = [WLTableViewCell]()
+        self.bookDetailsModel = bookDetails
+        let titleCell = BookDetailsHeaderTableViewCell.instance(height: bigHeaderHeight)
+        titleCell.setup(bookModel: bookDetails, topColor: topColor)
+        cellsArray.append(titleCell)
+        let infoCell = BookDetailsInfoTableViewCell.instance()
+        infoCell.setup(bookModel: bookDetails)
+        cellsArray.append(infoCell)
+        cellsArray.append(BookDetailsSeparatorTableViewCell.instance())
+        
+        if bookDetails.fragmentHtml.count > 0{
+            let fragmentCell = BookDetailsFragmentTableViewCell.instance()
+            fragmentCell.setup(fragmentTitle: bookDetails.fragmentTitle, fragmentHtml: bookDetails.fragmentHtml)
+            cellsArray.append( fragmentCell)
+            cellsArray.append(BookDetailsSeparatorTableViewCell.instance())
+
+        }
+        
+        if isBookPremium && DatabaseManager.shared.isUserPremium() == false{ // dont show buttons when user is not premium
+            
+        }
+        else{
+            
+            if bookDetails.epub.count > 0{
+                var buttonType = BookDetailsButtonType.download_ebook
+                switch DownloadManager.sharedInstance.checkEbookStatus(bookSlug: bookDetails.slug){
+                case .downloaded:
+                    buttonType = .download_ebook_read
+                case .downloading:
+                    DownloadManager.sharedInstance.delegate = self
+                    buttonType = .download_ebook_loading
+                default:
+                    break
+                }
+                readCell = BookDetailsButtonTableViewCell.instance(delegate: self, bookDetailsButtonType: buttonType, bookDetailsModel: bookDetails)
+                cellsArray.append(readCell!)
+            }
+            
+            if bookDetails.getAudiobookFilesUrls().count > 0 {
+                var buttonType = BookDetailsButtonType.download_audiobook
+                
+                switch DownloadManager.sharedInstance.checkAudiobookStatus(bookDetailsModel: bookDetails){
+                case .downloaded:
+                    buttonType = .download_audiobook_listen
+                case .downloading:
+                    DownloadManager.sharedInstance.delegate = self
+                    buttonType = .download_audiobook_loading
+                default:
+                    break
+                }
+                audiobookCell = BookDetailsButtonTableViewCell.instance(delegate: self, bookDetailsButtonType: buttonType, bookDetailsModel: bookDetails)
+                cellsArray.append(audiobookCell!)
+            }
+            
+//            cellsArray.append(BookDetailsButtonTableViewCell.instance(delegate: self, bookDetailsButtonType: .support_us, bookDetailsModel: bookDetails))
+        }
+    }
+    
+    func bookDetailsDownloaded(bookDetails: BookDetailsModel){
+        self.bookDetailsModel = bookDetails
+        tableView.isHidden = false
+        buttonsContainer.isHidden = false
+        headerView.alpha = 0
+        topColor = self.bookDetailsModel!.bgColor
+        headerLabel.text = bookDetails.title
+        createCells(bookDetails: self.bookDetailsModel!)
+        tableView.reloadData()
+    }
+    
+    func setFavourite(favourite: Bool){
+        isFavourite = favourite
+        refreshFavouriteButton()
+        
+        syncManager.setFavouriteState(slug: bookSlug, favourite: favourite) {[weak self] (result) in
+            
+            guard let strongSelf = self else{
+                return
+            }
+            
+            strongSelf.refreshButton.setIndicatorButtonState(state: .hidden)
+            switch result {
+            case .success/*(let model)*/:
+                strongSelf.isFavourite = favourite
+            case .failure/*(let error)*/:
+                
+                break
+            }
+        }
+    }
+    
+    func refreshFavouriteButton(){
+        favouriteButton.setImage(isFavourite ? #imageLiteral(resourceName: "icon_heart-fill-big") : #imageLiteral(resourceName: "icon_heart-outline-big"), for: .normal)
+    }
+    
+    func refreshIsFavourite(){
+        guard syncManager.isLoggedIn() else { return }
+        
+        syncManager.getFavouriteState(slug: bookSlug) {[weak self] (result) in
+            
+            guard let strongSelf = self else{
+                return
+            }
+            
+            switch result {
+            case .success(let model):
+                strongSelf.isFavourite = (model as! LikeModel).likes
+                strongSelf.refreshFavouriteButton()
+            case .failure/*(let error)*/:
+                break
+            }
+        }
+
+    }
+    
+    func refreshReadingState(){
+        
+        guard syncManager.isLoggedIn() else {
+            readingState = .unknown
+            return
+        }
+        
+        syncManager.getReadingState(slug: bookSlug, completionHandler: { [weak self] (result) in
+        
+            guard let strongSelf = self else{
+                return
+            }
+            
+            switch result {
+            case .success(let model):
+                strongSelf.readingState = (model as! ReadingStateModel).state
+            case .failure/*(let error)*/:
+                break
+            }
+        })
+    }
+
+    
+    func refreshData(){
+        var storedBook = false
+        if let downloadedModel = DatabaseManager.shared.getBookFromDownloaded(bookSlug: bookSlug) {
+            storedBook = true
+            bookDetailsDownloaded(bookDetails: downloadedModel)
+        }
+
+        if storedBook == false {
+            refreshButton.setIndicatorButtonState(state: .loading)
+        }
+        
+        syncManager.getBookDetails(bookSlug: bookSlug) {[weak self] (result) in
+            
+            guard let strongSelf = self else{
+                return
+            }
+            
+            strongSelf.refreshButton.setIndicatorButtonState(state: .hidden)
+            switch result {
+            case .success(let model):
+                if let model = model as? BookDetailsModel{
+                    model.slug = strongSelf.bookSlug
+                    if storedBook {
+                        DatabaseManager.shared.addBookToDownloaded(bookDetailsModel: model)
+                    }
+                    strongSelf.bookDetailsDownloaded(bookDetails: model)
+                }
+                
+            case .failure/*(let error)*/:
+                if storedBook == false {
+                    strongSelf.refreshButton.setIndicatorButtonState(state: .button)
+                    self?.view.makeToast("book_loading_error".localized, duration: 3.0, position: .bottom)
+                }
+            }
+        }
+    }
+    
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        openedPlayer = false
+        navigationController?.setNavigationBarHidden(true, animated: true)
+        DownloadManager.sharedInstance.delegate = self
+    }
+    
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        if !openedPlayer {
+            navigationController?.setNavigationBarHidden(false, animated: true)
+        }
+        DownloadManager.sharedInstance.delegate = nil
+    }
+    
+    func downloadEbook(){
+        guard let bookDetailsModel = bookDetailsModel, bookDetailsModel.epub.count > 0 else {return}
+        
+        DatabaseManager.shared.addBookToDownloaded(bookDetailsModel: bookDetailsModel)
+        DownloadManager.sharedInstance.delegate = self
+        DownloadManager.sharedInstance.downloadEbook(bookDetailsModel: bookDetailsModel)
+    }
+    
+    var openedPlayer = false
+    func downloadAudiobooks(){
+        guard let bookDetailsModel = bookDetailsModel, bookDetailsModel.getAudiobookFilesUrls().count > 0 else {return}
+        
+        DatabaseManager.shared.addBookToDownloaded(bookDetailsModel: bookDetailsModel)
+
+        DownloadManager.sharedInstance.delegate = self
+        DownloadManager.sharedInstance.downloadAudiobooks(bookDetailsModel: bookDetailsModel)
+    }
+
+    func updateReadingStateIfNeeded(state: ReadingStateModel.ReadingState) {
+        if state == .reading && readingState != .not_started {
+            return
+        }
+        
+        if state == .complete && readingState == .complete {
+            return
+        }
+
+        syncManager.setReadingState(slug: bookSlug, readingState: state, completionHandler: nil)
+    }
+    
+    func openFolioReader() {
+        guard ebookExists(bookSlug: bookSlug) else {
+            return
+        }
+
+        updateReadingStateIfNeeded(state: .reading)
+        
+        var array = parentNames()
+        array.append("Reader")
+        MatomoTracker.shared.track(view: array)
+
+        let config = WLReaderConfig()
+        let bookPath = FileType.ebook.pathForFileName(filename: bookSlug, bookSlug: bookSlug)
+        let bookDirectory = FileType.ebook.destinationPathWithSlug(bookSlug: bookSlug) + "unzipped/"
+
+        if !FileManager.default.fileExists(atPath: bookDirectory) {
+            try! FileManager.default.createDirectory(atPath: bookDirectory, withIntermediateDirectories: true, attributes: nil)
+        }
+
+        let folioReader = FolioReader()
+        folioReader.delegate = self
+        
+        folioReader.presentReader(parentViewController: self.navigationController!, withEpubPath: bookPath, unzipPath: bookDirectory, andConfig: config, shouldRemoveEpub: false)
+    }
+    
+    func openAudiobook(afterDownload: Bool) {
+        guard let bookDetailsModel = bookDetailsModel else { return }
+        
+        updateReadingStateIfNeeded(state: .reading)
+
+        openedPlayer = true
+        navigationController?.pushViewController(PlayerViewController.instance(bookDetailsModel: bookDetailsModel), animated: true)
+    }
+    
+    deinit {
+    }
+}
+
+extension BookDetailsViewController: UITableViewDataSource{
+    
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return cellsArray.count
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        return cellsArray[indexPath.row]
+    }
+}
+
+extension BookDetailsViewController: UITableViewDelegate{
+    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        return cellsArray[indexPath.row].getHeight()
+    }
+    
+    func scrollViewDidScroll(_ scrollView: UIScrollView) {
+        print(scrollView.contentOffset)
+        
+        var progress: CGFloat = 100
+
+        var newValue = bigHeaderHeight - scrollView.contentOffset.y
+        if newValue < smallHeaderHeight{
+            newValue = smallHeaderHeight
+            progress = 0
+        }
+        else{
+            progress = (bigHeaderHeight - smallHeaderHeight - scrollView.contentOffset.y) / (bigHeaderHeight - smallHeaderHeight)
+            if progress > 100{
+                progress = 100
+            }
+        }
+        
+        headerView.alpha = 1 - progress
+        buttonsContainer.alpha = progress
+        headerHeightConstraint.constant = newValue
+        headerProgress = progress
+    }
+    
+    func showHeader(){
+        headerView.alpha = 1
+        buttonsContainer.alpha = 0
+        headerHeightConstraint.constant = smallHeaderHeight
+        headerProgress = 0
+    }
+}
+
+extension BookDetailsViewController: BookDetailsButtonTableViewCellDelegate{
+    
+    func bookDetailsButtonTableViewCellButtonTapped(buttonType: BookDetailsButtonType){
+        switch buttonType {
+        case .download_ebook:
+            downloadEbook()
+        case .download_ebook_read:
+            openFolioReader()
+        case .download_audiobook:
+            downloadAudiobooks()
+        case .download_audiobook_listen:
+            openAudiobook(afterDownload: false)
+        default:
+            break
+        }
+    }
+    
+    func bookDetailsButtonTableViewCellDeleteButtonTapped(buttonType: BookDetailsButtonType){
+        switch buttonType {
+        case .download_ebook_read, .download_ebook_loading:
+            DownloadManager.sharedInstance.deleteEbook(bookSlug: bookSlug)
+            readCell?.setup(bookDetailsButtonType: .download_ebook, progress: nil, bookDetailsModel: bookDetailsModel)
+            bookDetailsModel = (bookDetailsModel?.copy() as! BookDetailsModel)
+            let _ = DatabaseManager.shared.removeBookFromDownloaded(bookSlug: bookSlug)
+        case .download_audiobook_listen, .download_audiobook_loading:
+            if let bookDetails = PlayerController.shared.currentBookDetails, bookDetails.slug == bookSlug{
+                PlayerController.shared.stopAndClear()
+            }
+            DownloadManager.sharedInstance.clearDownloadingAudiobookFromQueue(bookSlug: bookSlug)
+            DownloadManager.sharedInstance.deleteAudiobook(bookSlug: bookSlug)
+            audiobookCell?.setup(bookDetailsButtonType: .download_audiobook, progress:nil, bookDetailsModel: bookDetailsModel)
+            bookDetailsModel = (bookDetailsModel?.copy() as! BookDetailsModel)
+            let _ = DatabaseManager.shared.removeBookFromDownloaded(bookSlug: bookSlug)
+        default:
+            break
+        }
+    }
+}
+
+extension BookDetailsViewController: DownloadManagerDelegate{
+    
+    func downloadManagerDownloadProgressChanged(model: MZDownloadModel, allProgress: Float, bookSlug: String) {
+        guard let bookDetailsModel = bookDetailsModel, bookDetailsModel.slug == bookSlug else { return        }
+
+        if model.isAudiobook() {
+            audiobookCell?.setup(bookDetailsButtonType: .download_audiobook_loading, progress: allProgress,  bookDetailsModel: bookDetailsModel)
+        }
+        else if model.isEbook() {
+            readCell?.setup(bookDetailsButtonType: .download_ebook_loading, progress: model.progress,  bookDetailsModel: bookDetailsModel)
+        }
+    }
+    
+    func downloadManagerDownloadFinished(model: MZDownloadModel, bookSlug: String) {
+        guard let bookDetailsModel = bookDetailsModel, bookDetailsModel.slug == bookSlug else { return        }
+        
+        if model.isAudiobook() {
+            
+            bookDetailsModel.setInitialAudiobookChaptersValuesIfNeeded()
+            
+            audiobookCell?.setup(bookDetailsButtonType: .download_audiobook_listen, progress: nil,  bookDetailsModel: bookDetailsModel)
+            openAudiobook(afterDownload: true)
+        }
+        else if model.isEbook() {
+            readCell?.setup(bookDetailsButtonType: .download_ebook_read, progress: nil,  bookDetailsModel: bookDetailsModel)
+            openFolioReader()
+        }
+    }
+    
+    func downloadManagerDownloadFailed(model: MZDownloadModel, bookSlug: String) {
+        guard let bookDetailsModel = bookDetailsModel, bookDetailsModel.slug == bookSlug else { return }
+
+        if model.isAudiobook() {
+            view.makeToast("audiobook_download_error".localized, duration: 3.0, position: .bottom)
+            audiobookCell?.setup(bookDetailsButtonType: .download_audiobook, progress: nil, bookDetailsModel: bookDetailsModel)
+        }
+        else if model.isEbook() {
+            view.makeToast("book_download_error".localized, duration: 3.0, position: .bottom)
+            readCell?.setup(bookDetailsButtonType: .download_ebook, progress: nil, bookDetailsModel: bookDetailsModel)
+        }
+    }
+}
+
+extension BookDetailsViewController: FolioReaderDelegate{
+    @objc func folioReaderDidClose(_ folioReader: FolioReader) {
+        guard let bookDetailsModel = bookDetailsModel, let progressValues = folioReader.getProgressValues(), progressValues.currentPage > 0, progressValues.currentPage <= progressValues.totalPages else { return }
+        
+        DatabaseManager.shared.updateCurrentChapters(bookDetailsModel: bookDetailsModel, currentChapter: progressValues.currentPage, totalChapters: progressValues.totalPages, currentAudioChapter: nil, totalAudioChapters: nil)
+        readCell?.setup(bookDetailsButtonType: .download_ebook_read, progress: nil, bookDetailsModel: bookDetailsModel)
+        
+        if progressValues.currentPage == progressValues.totalPages && readingState == ReadingStateModel.ReadingState.reading{
+           updateReadingStateIfNeeded(state: .complete)
+        }
+    }
+}