// // AppContentViewController.swift // AltStore // // Created by Riley Testut on 7/22/19. // Copyright © 2019 Riley Testut. All rights reserved. // import UIKit import Roxas import Nuke extension AppContentViewController { private enum Row: Int, CaseIterable { case subtitle case screenshots case description case versionDescription case permissions } } class AppContentViewController: UITableViewController { var app: StoreApp! private lazy var screenshotsDataSource = self.makeScreenshotsDataSource() private lazy var permissionsDataSource = self.makePermissionsDataSource() private lazy var dateFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium dateFormatter.timeStyle = .none return dateFormatter }() private lazy var byteCountFormatter: ByteCountFormatter = { let formatter = ByteCountFormatter() return formatter }() @IBOutlet private var subtitleLabel: UILabel! @IBOutlet private var descriptionTextView: CollapsingTextView! @IBOutlet private var versionDescriptionTextView: CollapsingTextView! @IBOutlet private var versionLabel: UILabel! @IBOutlet private var versionDateLabel: UILabel! @IBOutlet private var sizeLabel: UILabel! @IBOutlet private var screenshotsCollectionView: UICollectionView! @IBOutlet private var permissionsCollectionView: UICollectionView! var preferredScreenshotSize: CGSize? { let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout let aspectRatio: CGFloat = 16.0 / 9.0 // Hardcoded for now. let width = self.screenshotsCollectionView.bounds.width - (layout.minimumInteritemSpacing * 2) let itemWidth = width / 1.5 let itemHeight = itemWidth * aspectRatio return CGSize(width: itemWidth, height: itemHeight) } override func viewDidLoad() { super.viewDidLoad() self.tableView.contentInset.bottom = 20 self.screenshotsCollectionView.dataSource = self.screenshotsDataSource self.screenshotsCollectionView.prefetchDataSource = self.screenshotsDataSource self.permissionsCollectionView.dataSource = self.permissionsDataSource self.subtitleLabel.text = self.app.subtitle self.descriptionTextView.text = self.app.localizedDescription self.versionDescriptionTextView.text = self.app.versionDescription self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), self.app.version) self.versionDateLabel.text = Date().relativeDateString(since: self.app.versionDate, dateFormatter: self.dateFormatter) self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: Int64(self.app.size)) self.descriptionTextView.maximumNumberOfLines = 5 self.descriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered) self.versionDescriptionTextView.maximumNumberOfLines = 3 self.versionDescriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() guard var size = self.preferredScreenshotSize else { return } size.height = min(size.height, self.screenshotsCollectionView.bounds.height) // Silence temporary "item too tall" warning. let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout layout.itemSize = size } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { guard segue.identifier == "showPermission" else { return } guard let cell = sender as? UICollectionViewCell, let indexPath = self.permissionsCollectionView.indexPath(for: cell) else { return } let permission = self.permissionsDataSource.item(at: indexPath) let maximumWidth = self.view.bounds.width - 20 let permissionPopoverViewController = segue.destination as! PermissionPopoverViewController permissionPopoverViewController.permission = permission permissionPopoverViewController.view.widthAnchor.constraint(lessThanOrEqualToConstant: maximumWidth).isActive = true let size = permissionPopoverViewController.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) permissionPopoverViewController.preferredContentSize = size permissionPopoverViewController.popoverPresentationController?.delegate = self permissionPopoverViewController.popoverPresentationController?.sourceRect = cell.frame permissionPopoverViewController.popoverPresentationController?.sourceView = self.permissionsCollectionView } } private extension AppContentViewController { func makeScreenshotsDataSource() -> RSTArrayCollectionViewPrefetchingDataSource { let dataSource = RSTArrayCollectionViewPrefetchingDataSource(items: self.app.screenshotURLs as [NSURL]) dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in let cell = cell as! ScreenshotCollectionViewCell cell.imageView.image = nil cell.imageView.isIndicatingActivity = true } dataSource.prefetchHandler = { (imageURL, indexPath, completionHandler) in return RSTAsyncBlockOperation() { (operation) in ImagePipeline.shared.loadImage(with: imageURL as URL, progress: nil, completion: { (response, error) in guard !operation.isCancelled else { return operation.finish() } if let image = response?.image { completionHandler(image, nil) } else { completionHandler(nil, error) } }) } } dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in let cell = cell as! ScreenshotCollectionViewCell cell.imageView.isIndicatingActivity = false cell.imageView.image = image if let error = error { print("Error loading image:", error) } } return dataSource } func makePermissionsDataSource() -> RSTArrayCollectionViewDataSource { let dataSource = RSTArrayCollectionViewDataSource(items: self.app.permissions) dataSource.cellConfigurationHandler = { (cell, permission, indexPath) in let cell = cell as! PermissionCollectionViewCell cell.button.setImage(permission.type.icon, for: .normal) cell.textLabel.text = permission.type.localizedShortName } return dataSource } } private extension AppContentViewController { @objc func toggleCollapsingSection(_ sender: UIButton) { let indexPath: IndexPath switch sender { case self.descriptionTextView.moreButton: indexPath = IndexPath(row: Row.description.rawValue, section: 0) case self.versionDescriptionTextView.moreButton: indexPath = IndexPath(row: Row.versionDescription.rawValue, section: 0) default: return } // Disable animations to prevent some potentially strange ones. UIView.performWithoutAnimation { self.tableView.reloadRows(at: [indexPath], with: .none) } } } extension AppContentViewController { override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { cell.tintColor = self.app.tintColor } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { guard indexPath.row == Row.screenshots.rawValue else { return super.tableView(tableView, heightForRowAt: indexPath) } guard let size = self.preferredScreenshotSize else { return 0.0 } return size.height } } extension AppContentViewController: UIPopoverPresentationControllerDelegate { func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle { return .none } }