MediaCategoryViewController.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  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. class VLCMediaCategoryViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UISearchResultsUpdating, UISearchControllerDelegate, IndicatorInfoProvider {
  15. var model: MediaLibraryBaseModel
  16. private var services: Services
  17. private var searchController: UISearchController?
  18. private let searchDataSource = VLCLibrarySearchDisplayDataSource()
  19. private lazy var editController: VLCEditController = {
  20. let editController = VLCEditController(mediaLibraryService:services.medialibraryService, model: model)
  21. editController.delegate = self
  22. return editController
  23. }()
  24. private var editToolbarConstraint: NSLayoutConstraint?
  25. private var cachedCellSize = CGSize.zero
  26. // @available(iOS 11.0, *)
  27. // lazy var dragAndDropManager: VLCDragAndDropManager = { () -> VLCDragAndDropManager<T> in
  28. // VLCDragAndDropManager<T>(subcategory: VLCMediaSubcategories<>)
  29. // }()
  30. lazy var emptyView: VLCEmptyLibraryView = {
  31. let name = String(describing: VLCEmptyLibraryView.self)
  32. let nib = Bundle.main.loadNibNamed(name, owner: self, options: nil)
  33. guard let emptyView = nib?.first as? VLCEmptyLibraryView else { fatalError("Can't find nib for \(name)") }
  34. return emptyView
  35. }()
  36. let editCollectionViewLayout: UICollectionViewFlowLayout = {
  37. let editCollectionViewLayout = UICollectionViewFlowLayout()
  38. editCollectionViewLayout.minimumLineSpacing = 1
  39. editCollectionViewLayout.minimumInteritemSpacing = 0
  40. return editCollectionViewLayout
  41. }()
  42. @available(*, unavailable)
  43. init() {
  44. fatalError()
  45. }
  46. init(services: Services, model: MediaLibraryBaseModel) {
  47. self.services = services
  48. self.model = model
  49. super.init(collectionViewLayout: UICollectionViewFlowLayout())
  50. NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .VLCThemeDidChangeNotification, object: nil)
  51. }
  52. override var preferredStatusBarStyle: UIStatusBarStyle {
  53. return PresentationTheme.current.colors.statusBarStyle
  54. }
  55. @objc func reloadData() {
  56. DispatchQueue.main.async {
  57. [weak self] in
  58. self?.collectionView?.reloadData()
  59. self?.updateUIForContent()
  60. }
  61. }
  62. @available(*, unavailable)
  63. required init?(coder aDecoder: NSCoder) {
  64. fatalError("init(coder: ) has not been implemented")
  65. }
  66. override func viewDidLoad() {
  67. super.viewDidLoad()
  68. setupCollectionView()
  69. setupSearchController()
  70. setupEditToolbar()
  71. _ = (MLMediaLibrary.sharedMediaLibrary() as! MLMediaLibrary).libraryDidAppear()
  72. }
  73. override func viewWillAppear(_ animated: Bool) {
  74. super.viewWillAppear(animated)
  75. let manager = services.rendererDiscovererManager
  76. if manager.discoverers.isEmpty {
  77. // Either didn't start or stopped before
  78. manager.start()
  79. }
  80. manager.presentingViewController = self
  81. }
  82. @objc func themeDidChange() {
  83. collectionView?.backgroundColor = PresentationTheme.current.colors.background
  84. editController.view.backgroundColor = PresentationTheme.current.colors.background
  85. setNeedsStatusBarAppearanceUpdate()
  86. }
  87. func setupEditToolbar() {
  88. editController.view.translatesAutoresizingMaskIntoConstraints = false
  89. view.addSubview(editController.view)
  90. editToolbarConstraint = editController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 60)
  91. NSLayoutConstraint.activate([
  92. editToolbarConstraint!,
  93. editController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
  94. editController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
  95. editController.view.heightAnchor.constraint(equalToConstant: 50)
  96. ])
  97. }
  98. override func viewDidAppear(_ animated: Bool) {
  99. super.viewDidAppear(animated)
  100. reloadData()
  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: - Edit
  119. override func setEditing(_ editing: Bool, animated: Bool) {
  120. super.setEditing(editing, animated: animated)
  121. // might have an issue if the old datasource was search
  122. // Most of the edit logic is handled inside editController
  123. collectionView?.dataSource = editing ? editController : self
  124. collectionView?.delegate = editing ? editController : self
  125. editController.resetSelections()
  126. displayEditToolbar()
  127. let layoutToBe = editing ? editCollectionViewLayout : UICollectionViewFlowLayout()
  128. collectionView?.setCollectionViewLayout(layoutToBe, animated: false, completion: {
  129. [weak self] finished in
  130. guard finished else {
  131. assertionFailure("VLCMediaSubcategoryViewController: Edit layout transition failed.")
  132. return
  133. }
  134. self?.reloadData()
  135. })
  136. }
  137. private func displayEditToolbar() {
  138. UIView.animate(withDuration: 0.3) { [weak self] in
  139. self?.editToolbarConstraint?.constant = self?.isEditing == true ? 0 : 60
  140. self?.view.layoutIfNeeded()
  141. }
  142. }
  143. // MARK: - Search
  144. func updateSearchResults(for searchController: UISearchController) {
  145. searchDataSource.shouldReloadTable(forSearch: searchController.searchBar.text, searchableFiles: model.anyfiles)
  146. collectionView?.reloadData()
  147. }
  148. func didPresentSearchController(_ searchController: UISearchController) {
  149. collectionView?.dataSource = searchDataSource
  150. }
  151. func didDismissSearchController(_ searchController: UISearchController) {
  152. collectionView?.dataSource = self
  153. }
  154. func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
  155. return IndicatorInfo(title:model.indicatorName)
  156. }
  157. // MARK: - UICollectionViewDataSource
  158. override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  159. return model.anyfiles.count
  160. }
  161. override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  162. guard let mediaCell = collectionView.dequeueReusableCell(withReuseIdentifier:model.cellType.defaultReuseIdentifier, for: indexPath) as? BaseCollectionViewCell else {
  163. assertionFailure("you forgot to register the cell or the cell is not a subclass of BaseCollectionViewCell")
  164. return UICollectionViewCell()
  165. }
  166. let mediaObject = model.anyfiles[indexPath.row]
  167. if let media = mediaObject as? VLCMLMedia {
  168. assert(media.mainFile() != nil, "The mainfile is nil")
  169. mediaCell.media = media.mainFile() != nil ? media : nil
  170. } else {
  171. mediaCell.media = mediaObject
  172. }
  173. return mediaCell
  174. }
  175. // MARK: - UICollectionViewDelegate
  176. override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  177. if let media = model.anyfiles[indexPath.row] as? VLCMLMedia {
  178. play(media: media)
  179. }
  180. }
  181. }
  182. // MARK: - UICollectionViewDelegateFlowLayout
  183. extension VLCMediaCategoryViewController {
  184. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
  185. if cachedCellSize == .zero {
  186. cachedCellSize = model.cellType.cellSizeForWidth(collectionView.frame.size.width)
  187. }
  188. return cachedCellSize
  189. }
  190. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
  191. return UIEdgeInsets(top: model.cellType.cellPadding, left: model.cellType.cellPadding, bottom: model.cellType.cellPadding, right: model.cellType.cellPadding)
  192. }
  193. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
  194. return model.cellType.cellPadding
  195. }
  196. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
  197. return model.cellType.cellPadding
  198. }
  199. override func handleSort() {
  200. let sortOptionsAlertController = UIAlertController(title: NSLocalizedString("SORT_BY", comment: ""),
  201. message: nil,
  202. preferredStyle: .actionSheet)
  203. var alertActions = [UIAlertAction]()
  204. for (index, enabled) in model.sortModel.sortingCriteria.enumerated() {
  205. guard enabled else { continue }
  206. let criteria = VLCMLSortingCriteria(value: UInt(index))
  207. alertActions.append(UIAlertAction(title: String(describing: criteria), style: .default) {
  208. [weak self] action in
  209. self?.model.sort(by: criteria)
  210. })
  211. }
  212. alertActions.forEach() { sortOptionsAlertController.addAction($0) }
  213. let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL", comment: ""),
  214. style: .cancel,
  215. handler: nil)
  216. sortOptionsAlertController.addAction(cancelAction)
  217. sortOptionsAlertController.view.tintColor = UIColor.vlcOrangeTint
  218. sortOptionsAlertController.popoverPresentationController?.sourceView = self.view
  219. present(sortOptionsAlertController, animated: true)
  220. }
  221. }
  222. extension VLCMediaCategoryViewController: VLCEditControllerDelegate {
  223. func editController(editController: VLCEditController, cellforItemAt indexPath: IndexPath) -> MediaEditCell? {
  224. return collectionView.cellForItem(at: indexPath) as? MediaEditCell
  225. }
  226. }
  227. private extension VLCMediaCategoryViewController {
  228. func setupCollectionView() {
  229. let cellNib = UINib(nibName: model.cellType.nibName, bundle: nil)
  230. collectionView?.register(cellNib, forCellWithReuseIdentifier: model.cellType.defaultReuseIdentifier)
  231. if let editCell = (model as? EditableMLModel)?.editCellType() {
  232. let editCellNib = UINib(nibName: editCell.nibName, bundle: nil)
  233. collectionView?.register(editCellNib, forCellWithReuseIdentifier: editCell.defaultReuseIdentifier)
  234. }
  235. collectionView?.backgroundColor = PresentationTheme.current.colors.background
  236. collectionView?.alwaysBounceVertical = true
  237. if #available(iOS 11.0, *) {
  238. collectionView?.contentInsetAdjustmentBehavior = .always
  239. // collectionView?.dragDelegate = dragAndDropManager
  240. // collectionView?.dropDelegate = dragAndDropManager
  241. }
  242. }
  243. func setupSearchController() {
  244. searchController = UISearchController(searchResultsController: nil)
  245. searchController?.searchResultsUpdater = self
  246. searchController?.dimsBackgroundDuringPresentation = false
  247. searchController?.delegate = self
  248. if let textfield = searchController?.searchBar.value(forKey: "searchField") as? UITextField {
  249. if let backgroundview = textfield.subviews.first {
  250. backgroundview.backgroundColor = UIColor.white
  251. backgroundview.layer.cornerRadius = 10
  252. backgroundview.clipsToBounds = true
  253. }
  254. }
  255. }
  256. }
  257. // MARK: - Player
  258. extension VLCMediaCategoryViewController {
  259. func play(mediaObject: NSManagedObject) {
  260. VLCPlaybackController.sharedInstance().playMediaLibraryObject(mediaObject)
  261. }
  262. func play(media: VLCMLMedia) {
  263. VLCPlaybackController.sharedInstance().fullscreenSessionRequested = media.subtype() != .albumTrack
  264. VLCPlaybackController.sharedInstance().play(media)
  265. }
  266. }
  267. // MARK: - MediaLibraryModelView
  268. extension VLCMediaCategoryViewController {
  269. func dataChanged() {
  270. reloadData()
  271. }
  272. }