MediaCategoryViewController.swift 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /*****************************************************************************
  2. * MediaCateogoryViewController.swift
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2018 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Carola Nitz <nitz.carola # gmail.com>
  9. * Mike JS. Choi <mkchoi212 # icloud.com>
  10. *
  11. * Refer to the COPYING file of the official project for license.
  12. *****************************************************************************/
  13. import Foundation
  14. protocol VLCMediaCategoryViewControllerDelegate: class {
  15. func mediaViewControllerDidSelectMediaObject(_ mediaViewController: UIViewController, mediaObject: NSManagedObject)
  16. }
  17. class VLCMediaCategoryViewController<T>: UICollectionViewController, UICollectionViewDelegateFlowLayout, UISearchResultsUpdating, UISearchControllerDelegate, IndicatorInfoProvider {
  18. let cellPadding: CGFloat = 5.0
  19. private var services: Services
  20. private var searchController: UISearchController?
  21. private let searchDataSource = VLCLibrarySearchDisplayDataSource()
  22. var subcategory: VLCMediaSubcategoryModel<T>
  23. weak var delegate: VLCMediaCategoryViewControllerDelegate?
  24. @available(iOS 11.0, *)
  25. lazy var dragAndDropManager: VLCDragAndDropManager = { () -> VLCDragAndDropManager<T> in
  26. VLCDragAndDropManager<T>(subcategory: subcategory)
  27. }()
  28. lazy var emptyView: VLCEmptyLibraryView = {
  29. let name = String(describing: VLCEmptyLibraryView.self)
  30. let nib = Bundle.main.loadNibNamed(name, owner: self, options: nil)
  31. guard let emptyView = nib?.first as? VLCEmptyLibraryView else { fatalError("Can't find nib for \(name)") }
  32. return emptyView
  33. }()
  34. @available(*, unavailable)
  35. init() {
  36. fatalError()
  37. }
  38. init(services: Services, subcategory: VLCMediaSubcategoryModel<T>) {
  39. self.services = services
  40. self.subcategory = subcategory
  41. super.init(collectionViewLayout: UICollectionViewFlowLayout())
  42. NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .VLCThemeDidChangeNotification, object: nil)
  43. NotificationCenter.default.addObserver(self, selector: #selector(reloadData), name: subcategory.changeNotificationName, object: nil)
  44. }
  45. override var preferredStatusBarStyle: UIStatusBarStyle {
  46. return PresentationTheme.current.colors.statusBarStyle
  47. }
  48. @objc func reloadData() {
  49. collectionView?.reloadData()
  50. updateUIForContent()
  51. }
  52. @available(*, unavailable)
  53. required init?(coder aDecoder: NSCoder) {
  54. fatalError("init(coder: ) has not been implemented")
  55. }
  56. override func viewDidLoad() {
  57. super.viewDidLoad()
  58. setupCollectionView()
  59. setupSearchController()
  60. _ = (MLMediaLibrary.sharedMediaLibrary() as! MLMediaLibrary).libraryDidAppear()
  61. }
  62. override func viewWillAppear(_ animated: Bool) {
  63. super.viewWillAppear(animated)
  64. let manager = services.rendererDiscovererManager
  65. if manager.discoverers.isEmpty {
  66. // Either didn't start or stopped before
  67. manager.start()
  68. }
  69. manager.presentingViewController = self
  70. }
  71. @objc func themeDidChange() {
  72. collectionView?.backgroundColor = PresentationTheme.current.colors.background
  73. setNeedsStatusBarAppearanceUpdate()
  74. }
  75. func setupCollectionView() {
  76. let playlistnib = UINib(nibName: "VLCPlaylistCollectionViewCell", bundle: nil)
  77. collectionView?.register(playlistnib, forCellWithReuseIdentifier: VLCPlaylistCollectionViewCell.cellIdentifier())
  78. collectionView?.backgroundColor = PresentationTheme.current.colors.background
  79. collectionView?.alwaysBounceVertical = true
  80. if #available(iOS 11.0, *) {
  81. collectionView?.dragDelegate = dragAndDropManager
  82. collectionView?.dropDelegate = dragAndDropManager
  83. }
  84. }
  85. override func viewDidAppear(_ animated: Bool) {
  86. super.viewDidAppear(animated)
  87. reloadData()
  88. }
  89. func setupSearchController() {
  90. searchController = UISearchController(searchResultsController: nil)
  91. searchController?.searchResultsUpdater = self
  92. searchController?.dimsBackgroundDuringPresentation = false
  93. searchController?.delegate = self
  94. if let textfield = searchController?.searchBar.value(forKey: "searchField") as? UITextField {
  95. if let backgroundview = textfield.subviews.first {
  96. backgroundview.backgroundColor = UIColor.white
  97. backgroundview.layer.cornerRadius = 10
  98. backgroundview.clipsToBounds = true
  99. }
  100. }
  101. }
  102. func updateUIForContent() {
  103. let isEmpty = collectionView?.numberOfItems(inSection: 0) == 0
  104. if isEmpty {
  105. collectionView?.setContentOffset(.zero, animated: false)
  106. }
  107. collectionView?.backgroundView = isEmpty ? emptyView : nil
  108. if #available(iOS 11.0, *) {
  109. navigationItem.searchController = isEmpty ? nil : searchController
  110. } else {
  111. navigationItem.titleView = isEmpty ? nil : searchController?.searchBar
  112. }
  113. }
  114. // MARK: Renderer
  115. override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
  116. collectionView?.collectionViewLayout.invalidateLayout()
  117. }
  118. // MARK: - Search
  119. func updateSearchResults(for searchController: UISearchController) {
  120. searchDataSource.shouldReloadTable(forSearch: searchController.searchBar.text, searchableFiles: subcategory.files)
  121. collectionView?.reloadData()
  122. }
  123. func didPresentSearchController(_ searchController: UISearchController) {
  124. collectionView?.dataSource = searchDataSource
  125. }
  126. func didDismissSearchController(_ searchController: UISearchController) {
  127. collectionView?.dataSource = self
  128. }
  129. func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
  130. return IndicatorInfo(title:subcategory.indicatorInfoName)
  131. }
  132. // MARK: - UICollectionViewDataSource
  133. override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  134. return subcategory.files.count
  135. }
  136. override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  137. if let playlistCell = collectionView.dequeueReusableCell(withReuseIdentifier: VLCPlaylistCollectionViewCell.cellIdentifier(), for: indexPath) as? VLCPlaylistCollectionViewCell {
  138. if let mediaObject = subcategory.files[indexPath.row] as? NSManagedObject {
  139. playlistCell.mediaObject = mediaObject
  140. }
  141. return playlistCell
  142. }
  143. return UICollectionViewCell()
  144. }
  145. // MARK: - UICollectionViewDelegate
  146. override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  147. if let mediaObject = subcategory.files[indexPath.row] as? NSManagedObject {
  148. delegate?.mediaViewControllerDidSelectMediaObject(self, mediaObject: mediaObject)
  149. }
  150. }
  151. // MARK: - UICollectionViewDelegateFlowLayout
  152. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
  153. let numberOfCells: CGFloat = collectionView.traitCollection.horizontalSizeClass == .regular ? 3.0 : 2.0
  154. let aspectRatio: CGFloat = 10.0 / 16.0
  155. // We have the number of cells and we always have numberofCells + 1 padding spaces. -pad-[Cell]-pad-[Cell]-pad-
  156. // we then have the entire padding, we divide the entire padding by the number of Cells to know how much needs to be substracted from ech cell
  157. // since this might be an uneven number we ceil
  158. var cellWidth = collectionView.bounds.size.width / numberOfCells
  159. cellWidth = cellWidth - ceil(((numberOfCells + 1) * cellPadding) / numberOfCells)
  160. return CGSize(width: cellWidth, height: cellWidth * aspectRatio)
  161. }
  162. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
  163. return UIEdgeInsets(top: cellPadding, left: cellPadding, bottom: cellPadding, right: cellPadding)
  164. }
  165. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
  166. return cellPadding
  167. }
  168. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
  169. return cellPadding
  170. }
  171. }