VLCEditController.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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. }
  14. class VLCEditController: UIViewController {
  15. private var selectedCellIndexPaths = Set<IndexPath>()
  16. private let model: MediaLibraryBaseModel
  17. private let mediaLibraryService: MediaLibraryService
  18. weak var delegate: VLCEditControllerDelegate?
  19. override func loadView() {
  20. let editToolbar = VLCEditToolbar(category: model)
  21. editToolbar.delegate = self
  22. self.view = editToolbar
  23. }
  24. init(mediaLibraryService: MediaLibraryService, model: MediaLibraryBaseModel) {
  25. self.mediaLibraryService = mediaLibraryService
  26. self.model = model
  27. super.init(nibName: nil, bundle: nil)
  28. }
  29. required init?(coder aDecoder: NSCoder) {
  30. fatalError("init(coder:) has not been implemented")
  31. }
  32. func resetSelections() {
  33. selectedCellIndexPaths.removeAll(keepingCapacity: false)
  34. }
  35. }
  36. // MARK: - Helpers
  37. private extension VLCEditController {
  38. private struct TextFieldAlertInfo {
  39. var alertTitle: String
  40. var alertDescription: String
  41. var placeHolder: String
  42. var confirmActionTitle: String
  43. init(alertTitle: String = "",
  44. alertDescription: String = "",
  45. placeHolder: String = "",
  46. confirmActionTitle: String = NSLocalizedString("BUTTON_DONE", comment: "")) {
  47. self.alertTitle = alertTitle
  48. self.alertDescription = alertDescription
  49. self.placeHolder = placeHolder
  50. self.confirmActionTitle = confirmActionTitle
  51. }
  52. }
  53. private func presentTextFieldAlert(with info: TextFieldAlertInfo,
  54. completionHandler: @escaping (String) -> Void) {
  55. let alertController = UIAlertController(title: info.alertTitle,
  56. message: info.alertDescription,
  57. preferredStyle: .alert)
  58. alertController.addTextField(configurationHandler: {
  59. textField in
  60. textField.placeholder = info.placeHolder
  61. })
  62. let cancelButton = UIAlertAction(title: NSLocalizedString("BUTTON_CANCEL", comment: ""),
  63. style: .default)
  64. let confirmAction = UIAlertAction(title: info.confirmActionTitle, style: .default) {
  65. [weak alertController] _ in
  66. guard let alertController = alertController,
  67. let textField = alertController.textFields?.first else { return }
  68. completionHandler(textField.text ?? "")
  69. }
  70. alertController.addAction(cancelButton)
  71. alertController.addAction(confirmAction)
  72. present(alertController, animated: true, completion: nil)
  73. }
  74. }
  75. // MARK: - VLCEditToolbarDelegate
  76. extension VLCEditController: VLCEditToolbarDelegate {
  77. func editToolbarDidAddToPlaylist(_ editToolbar: VLCEditToolbar) {
  78. //Todo: replace with Viewcontroller that shows existing Playlists
  79. let alertInfo = TextFieldAlertInfo(alertTitle: NSLocalizedString("PLAYLISTS", comment: ""),
  80. alertDescription: NSLocalizedString("PLAYLIST_DESCRIPTION", comment: ""),
  81. placeHolder: NSLocalizedString("PLAYLIST_PLACEHOLDER", comment:""))
  82. presentTextFieldAlert(with: alertInfo, completionHandler: {
  83. [weak self] text -> Void in
  84. guard let strongSelf = self else {
  85. return
  86. }
  87. let playlist = strongSelf.mediaLibraryService.createPlaylist(with: text)
  88. for indexPath in strongSelf.selectedCellIndexPaths {
  89. guard let media = strongSelf.model.anyfiles[indexPath.row] as? VLCMLMedia else {
  90. assertionFailure("we're not handling collections yet")
  91. return
  92. }
  93. playlist.appendMedia(withIdentifier: media.identifier())
  94. }
  95. })
  96. }
  97. func editToolbarDidDelete(_ editToolbar: VLCEditToolbar) {
  98. var objectsToDelete = [VLCMLObject]()
  99. for indexPath in selectedCellIndexPaths {
  100. objectsToDelete.append(model.anyfiles[indexPath.row])
  101. }
  102. let cancelButton = VLCAlertButton(title: NSLocalizedString("BUTTON_CANCEL", comment: ""))
  103. let deleteButton = VLCAlertButton(title: NSLocalizedString("BUTTON_DELETE", comment: ""),
  104. style: .destructive,
  105. action: {
  106. [weak self] action in
  107. self?.model.delete(objectsToDelete)
  108. self?.selectedCellIndexPaths.removeAll()
  109. })
  110. VLCAlertViewController.alertViewManager(title: NSLocalizedString("DELETE_TITLE", comment: ""),
  111. errorMessage: NSLocalizedString("DELETE_MESSAGE", comment: ""),
  112. viewController: (UIApplication.shared.keyWindow?.rootViewController)!,
  113. buttonsAction: [cancelButton,
  114. deleteButton])
  115. }
  116. func editToolbarDidShare(_ editToolbar: VLCEditToolbar, presentFrom button: UIButton) {
  117. UIApplication.shared.beginIgnoringInteractionEvents()
  118. let rootViewController = UIApplication.shared.keyWindow?.rootViewController
  119. guard let controller = VLCActivityViewControllerVendor.activityViewController(forFiles: fileURLsFromSelection(), presenting: button, presenting: rootViewController) else {
  120. UIApplication.shared.endIgnoringInteractionEvents()
  121. return
  122. }
  123. controller.popoverPresentationController?.sourceView = editToolbar
  124. rootViewController?.present(controller, animated: true) {
  125. UIApplication.shared.endIgnoringInteractionEvents()
  126. }
  127. }
  128. func fileURLsFromSelection() -> [URL] {
  129. var fileURLS = [URL]()
  130. for indexPath in selectedCellIndexPaths {
  131. guard let file = model.anyfiles[indexPath.row] as? VLCMLMedia else {
  132. assertionFailure("we're trying to share something that doesn't have an mrl")
  133. return fileURLS
  134. }
  135. fileURLS.append(file.mainFile().mrl)
  136. }
  137. return fileURLS
  138. }
  139. func editToolbarDidRename(_ editToolbar: VLCEditToolbar) {
  140. // FIXME: Multiple renaming of files(multiple alert can get unfriendly if too many files)
  141. for indexPath in selectedCellIndexPaths {
  142. if let media = model.anyfiles[indexPath.row] as? VLCMLMedia {
  143. // Not using VLCAlertViewController to have more customization in text fields
  144. let alertInfo = TextFieldAlertInfo(alertTitle: String(format: NSLocalizedString("RENAME_MEDIA_TO", comment: ""), media.title),
  145. placeHolder: NSLocalizedString("RENAME_PLACEHOLDER", comment: ""),
  146. confirmActionTitle: NSLocalizedString("BUTTON_RENAME", comment: ""))
  147. presentTextFieldAlert(with: alertInfo, completionHandler: {
  148. [weak self] text -> Void in
  149. guard text != "" else {
  150. VLCAlertViewController.alertViewManager(title: NSLocalizedString("ERROR_RENAME_FAILED", comment: ""),
  151. errorMessage: NSLocalizedString("ERROR_EMPTY_NAME", comment: ""),
  152. viewController: (UIApplication.shared.keyWindow?.rootViewController)!)
  153. return
  154. }
  155. media.updateTitle(text)
  156. if let strongself = self {
  157. strongself.delegate?.editController(editController: strongself, cellforItemAt: indexPath)?.isChecked = false
  158. }
  159. })
  160. }
  161. }
  162. }
  163. }
  164. // MARK: - UICollectionViewDataSource
  165. extension VLCEditController: UICollectionViewDataSource {
  166. func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  167. return model.anyfiles.count
  168. }
  169. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  170. guard let editCell = (model as? EditableMLModel)?.editCellType() else {
  171. assertionFailure("The category either doesn't implement EditableMLModel or doesn't have a editcellType defined")
  172. return UICollectionViewCell()
  173. }
  174. if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: editCell.defaultReuseIdentifier,
  175. for: indexPath) as? MediaEditCell {
  176. cell.media = model.anyfiles[indexPath.row]
  177. cell.isChecked = selectedCellIndexPaths.contains(indexPath)
  178. return cell
  179. } else {
  180. assertionFailure("We couldn't dequeue a reusable cell, the cell might not be registered or is not a MediaEditCell")
  181. return UICollectionViewCell()
  182. }
  183. }
  184. }
  185. // MARK: - UICollectionViewDelegate
  186. extension VLCEditController: UICollectionViewDelegate {
  187. func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  188. if let cell = collectionView.cellForItem(at: indexPath) as? MediaEditCell {
  189. cell.isChecked = !cell.isChecked
  190. if cell.isChecked {
  191. // cell selected, saving indexPath
  192. selectedCellIndexPaths.insert(indexPath)
  193. } else {
  194. selectedCellIndexPaths.remove(indexPath)
  195. }
  196. }
  197. }
  198. }
  199. // MARK: - UICollectionViewDelegateFlowLayout
  200. extension VLCEditController: UICollectionViewDelegateFlowLayout {
  201. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
  202. let contentInset = collectionView.contentInset
  203. // FIXME: 5 should be cell padding, but not usable maybe static?
  204. let insetToRemove = contentInset.left + contentInset.right + (5 * 2)
  205. return CGSize(width: collectionView.frame.width - insetToRemove, height: MediaEditCell.height)
  206. }
  207. }