VLCEditController.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. /*****************************************************************************
  2. * VLCEditController.swift
  3. *
  4. * Copyright © 2018 VLC authors and VideoLAN
  5. * Copyright © 2018 Videolabs
  6. *
  7. * Authors: Soomin Lee <bubu@mikan.io>
  8. *
  9. * Refer to the COPYING file of the official project for license.
  10. *****************************************************************************/
  11. protocol VLCEditControllerDelegate: class {
  12. func editController(editController: VLCEditController, cellforItemAt indexPath: IndexPath) -> MediaEditCell?
  13. func editController(editController: VLCEditController, present viewController: UIViewController)
  14. }
  15. class VLCEditController: UIViewController {
  16. private var selectedCellIndexPaths = Set<IndexPath>()
  17. private let model: MediaLibraryBaseModel
  18. private let mediaLibraryService: MediaLibraryService
  19. private lazy var addToPlaylistViewController: AddToPlaylistViewController = {
  20. var addToPlaylistViewController = AddToPlaylistViewController(playlists: mediaLibraryService.playlists())
  21. addToPlaylistViewController.delegate = self
  22. return addToPlaylistViewController
  23. }()
  24. weak var delegate: VLCEditControllerDelegate?
  25. override func loadView() {
  26. let editToolbar = VLCEditToolbar(category: model)
  27. editToolbar.delegate = self
  28. self.view = editToolbar
  29. }
  30. init(mediaLibraryService: MediaLibraryService, model: MediaLibraryBaseModel) {
  31. self.mediaLibraryService = mediaLibraryService
  32. self.model = model
  33. super.init(nibName: nil, bundle: nil)
  34. }
  35. required init?(coder aDecoder: NSCoder) {
  36. fatalError("init(coder:) has not been implemented")
  37. }
  38. func resetSelections() {
  39. selectedCellIndexPaths.removeAll(keepingCapacity: false)
  40. }
  41. }
  42. // MARK: - Helpers
  43. private extension VLCEditController {
  44. private struct TextFieldAlertInfo {
  45. var alertTitle: String
  46. var alertDescription: String
  47. var placeHolder: String
  48. var confirmActionTitle: String
  49. init(alertTitle: String = "",
  50. alertDescription: String = "",
  51. placeHolder: String = "",
  52. confirmActionTitle: String = NSLocalizedString("BUTTON_DONE", comment: "")) {
  53. self.alertTitle = alertTitle
  54. self.alertDescription = alertDescription
  55. self.placeHolder = placeHolder
  56. self.confirmActionTitle = confirmActionTitle
  57. }
  58. }
  59. private func presentTextFieldAlert(with info: TextFieldAlertInfo,
  60. completionHandler: @escaping (String) -> Void) {
  61. let alertController = UIAlertController(title: info.alertTitle,
  62. message: info.alertDescription,
  63. preferredStyle: .alert)
  64. alertController.addTextField(configurationHandler: {
  65. textField in
  66. textField.placeholder = info.placeHolder
  67. })
  68. let cancelButton = UIAlertAction(title: NSLocalizedString("BUTTON_CANCEL", comment: ""),
  69. style: .default)
  70. let confirmAction = UIAlertAction(title: info.confirmActionTitle, style: .default) {
  71. [weak alertController] _ in
  72. guard let alertController = alertController,
  73. let textField = alertController.textFields?.first else { return }
  74. completionHandler(textField.text ?? "")
  75. }
  76. alertController.addAction(cancelButton)
  77. alertController.addAction(confirmAction)
  78. present(alertController, animated: true, completion: nil)
  79. }
  80. }
  81. // MARK: - VLCEditToolbarDelegate
  82. extension VLCEditController: VLCEditToolbarDelegate {
  83. func addToNewPlaylist() {
  84. let alertInfo = TextFieldAlertInfo(alertTitle: NSLocalizedString("PLAYLISTS", comment: ""),
  85. placeHolder: NSLocalizedString("PLAYLIST_PLACEHOLDER",
  86. comment: ""))
  87. presentTextFieldAlert(with: alertInfo,
  88. completionHandler: {
  89. [unowned self] text -> Void in
  90. guard text != "" else {
  91. DispatchQueue.main.async {
  92. VLCAlertViewController.alertViewManager(title: NSLocalizedString("ERROR_EMPTY_NAME",
  93. comment: ""),
  94. viewController: self)
  95. }
  96. return
  97. }
  98. self.model.createPlaylist(text, self.selectedCellIndexPaths)
  99. self.selectedCellIndexPaths.removeAll()
  100. })
  101. }
  102. func editToolbarDidAddToPlaylist(_ editToolbar: VLCEditToolbar) {
  103. if !mediaLibraryService.playlists().isEmpty && !selectedCellIndexPaths.isEmpty {
  104. addToPlaylistViewController.playlists = mediaLibraryService.playlists()
  105. delegate?.editController(editController: self,
  106. present: addToPlaylistViewController)
  107. } else {
  108. addToNewPlaylist()
  109. }
  110. }
  111. func editToolbarDidDelete(_ editToolbar: VLCEditToolbar) {
  112. var objectsToDelete = [VLCMLObject]()
  113. for indexPath in selectedCellIndexPaths {
  114. objectsToDelete.append(model.anyfiles[indexPath.row])
  115. }
  116. let cancelButton = VLCAlertButton(title: NSLocalizedString("BUTTON_CANCEL", comment: ""))
  117. let deleteButton = VLCAlertButton(title: NSLocalizedString("BUTTON_DELETE", comment: ""),
  118. style: .destructive,
  119. action: {
  120. [weak self] action in
  121. self?.model.delete(objectsToDelete)
  122. self?.selectedCellIndexPaths.removeAll()
  123. })
  124. VLCAlertViewController.alertViewManager(title: NSLocalizedString("DELETE_TITLE", comment: ""),
  125. errorMessage: NSLocalizedString("DELETE_MESSAGE", comment: ""),
  126. viewController: (UIApplication.shared.keyWindow?.rootViewController)!,
  127. buttonsAction: [cancelButton,
  128. deleteButton])
  129. }
  130. func editToolbarDidShare(_ editToolbar: VLCEditToolbar, presentFrom button: UIButton) {
  131. UIApplication.shared.beginIgnoringInteractionEvents()
  132. let rootViewController = UIApplication.shared.keyWindow?.rootViewController
  133. guard let controller = VLCActivityViewControllerVendor.activityViewController(forFiles: fileURLsFromSelection(), presenting: button, presenting: rootViewController) else {
  134. UIApplication.shared.endIgnoringInteractionEvents()
  135. return
  136. }
  137. controller.popoverPresentationController?.sourceView = editToolbar
  138. rootViewController?.present(controller, animated: true) {
  139. UIApplication.shared.endIgnoringInteractionEvents()
  140. }
  141. }
  142. func fileURLsFromSelection() -> [URL] {
  143. var fileURLS = [URL]()
  144. for indexPath in selectedCellIndexPaths {
  145. let file = model.anyfiles[indexPath.row]
  146. if let collection = file as? MediaCollectionModel,
  147. let files = collection.files() {
  148. files.forEach {
  149. if let mainFile = $0.mainFile() {
  150. fileURLS.append(mainFile.mrl)
  151. }
  152. }
  153. } else if let media = file as? VLCMLMedia, let mainFile = media.mainFile() {
  154. fileURLS.append(mainFile.mrl)
  155. } else {
  156. assertionFailure("we're trying to share something that doesn't have an mrl")
  157. return fileURLS
  158. }
  159. }
  160. return fileURLS
  161. }
  162. func editToolbarDidRename(_ editToolbar: VLCEditToolbar) {
  163. // FIXME: Multiple renaming of files(multiple alert can get unfriendly if too many files)
  164. for indexPath in selectedCellIndexPaths {
  165. if let media = model.anyfiles[indexPath.row] as? VLCMLMedia {
  166. // Not using VLCAlertViewController to have more customization in text fields
  167. let alertInfo = TextFieldAlertInfo(alertTitle: String(format: NSLocalizedString("RENAME_MEDIA_TO", comment: ""), media.title),
  168. placeHolder: NSLocalizedString("RENAME_PLACEHOLDER", comment: ""),
  169. confirmActionTitle: NSLocalizedString("BUTTON_RENAME", comment: ""))
  170. presentTextFieldAlert(with: alertInfo, completionHandler: {
  171. [weak self] text -> Void in
  172. guard text != "" else {
  173. VLCAlertViewController.alertViewManager(title: NSLocalizedString("ERROR_RENAME_FAILED", comment: ""),
  174. errorMessage: NSLocalizedString("ERROR_EMPTY_NAME", comment: ""),
  175. viewController: (UIApplication.shared.keyWindow?.rootViewController)!)
  176. return
  177. }
  178. media.updateTitle(text)
  179. if let strongself = self {
  180. strongself.delegate?.editController(editController: strongself, cellforItemAt: indexPath)?.isChecked = false
  181. }
  182. })
  183. }
  184. }
  185. }
  186. }
  187. // MARK: - UICollectionViewDataSource
  188. extension VLCEditController: UICollectionViewDataSource {
  189. func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  190. return model.anyfiles.count
  191. }
  192. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  193. guard let editCell = (model as? EditableMLModel)?.editCellType() else {
  194. assertionFailure("The category either doesn't implement EditableMLModel or doesn't have a editcellType defined")
  195. return UICollectionViewCell()
  196. }
  197. if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: editCell.defaultReuseIdentifier,
  198. for: indexPath) as? MediaEditCell {
  199. cell.media = model.anyfiles[indexPath.row]
  200. cell.isChecked = selectedCellIndexPaths.contains(indexPath)
  201. return cell
  202. } else {
  203. assertionFailure("We couldn't dequeue a reusable cell, the cell might not be registered or is not a MediaEditCell")
  204. return UICollectionViewCell()
  205. }
  206. }
  207. func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
  208. guard let collectionModel = model as? CollectionModel, let playlist = collectionModel.mediaCollection as? VLCMLPlaylist else {
  209. assertionFailure("can Move should've been false")
  210. return
  211. }
  212. playlist.moveMedia(fromPosition: UInt32(sourceIndexPath.row), toDestination: UInt32(destinationIndexPath.row))
  213. }
  214. func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
  215. if let collectionModel = model as? CollectionModel, collectionModel.mediaCollection is VLCMLPlaylist {
  216. return true
  217. }
  218. return false
  219. }
  220. }
  221. // MARK: - UICollectionViewDelegate
  222. extension VLCEditController: UICollectionViewDelegate {
  223. func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  224. if let cell = collectionView.cellForItem(at: indexPath) as? MediaEditCell {
  225. cell.isChecked = !cell.isChecked
  226. if cell.isChecked {
  227. // cell selected, saving indexPath
  228. selectedCellIndexPaths.insert(indexPath)
  229. } else {
  230. selectedCellIndexPaths.remove(indexPath)
  231. }
  232. }
  233. }
  234. }
  235. // MARK: - UICollectionViewDelegateFlowLayout
  236. extension VLCEditController: UICollectionViewDelegateFlowLayout {
  237. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
  238. let contentInset = collectionView.contentInset
  239. // FIXME: 5 should be cell padding, but not usable maybe static?
  240. let insetToRemove = contentInset.left + contentInset.right + (5 * 2)
  241. return CGSize(width: collectionView.frame.width - insetToRemove, height: MediaEditCell.height)
  242. }
  243. }
  244. extension VLCEditController: AddToPlaylistViewControllerDelegate {
  245. func addToPlaylistViewController(_ addToPlaylistViewController: AddToPlaylistViewController,
  246. didSelectPlaylist playlist: VLCMLPlaylist) {
  247. let files = model.anyfiles
  248. for index in selectedCellIndexPaths where index.row < files.count {
  249. if !playlist.appendMedia(withIdentifier: files[index.row].identifier()) {
  250. assertionFailure("VLCEditController: AddToPlaylistViewControllerDelegate: Failed to add item.")
  251. }
  252. }
  253. addToPlaylistViewController.dismiss(animated: true, completion: nil)
  254. }
  255. func addToPlaylistViewController(_ addToPlaylistViewController: AddToPlaylistViewController,
  256. newPlaylistWithName name: String) {
  257. model.createPlaylist(name, self.selectedCellIndexPaths)
  258. selectedCellIndexPaths.removeAll()
  259. addToPlaylistViewController.dismiss(animated: true, completion: nil)
  260. }
  261. }