...
 
Commits (114)
......@@ -24,6 +24,7 @@ target 'VLC-iOS' do
pod 'PAPasscode', '~>1.0'
pod 'GoogleAPIClient/Drive'
pod 'MobileVLCKit', '3.1.3'
pod 'VLCMediaLibraryKit'
pod 'MediaLibraryKit-prod'
pod 'GTMAppAuth'
target 'VLC for iOSUITests' do
......
......@@ -55,6 +55,8 @@ PODS:
- upnpx (1.4.0)
- VLC-LiveSDK (5.7.0x)
- VLC-WhiteRaccoon (1.0.0)
- VLCMediaLibraryKit (0.0.4):
- MobileVLCKit
- XKKeychain (1.0.1)
- xmlrpc (2.3.4):
- "NSData+Base64 (~> 1.0.0)"
......@@ -81,6 +83,7 @@ DEPENDENCIES:
- upnpx (~> 1.4.0)
- VLC-LiveSDK (= 5.7.0x)
- VLC-WhiteRaccoon
- VLCMediaLibraryKit
- XKKeychain (~> 1.0)
SPEC REPOS:
......@@ -105,6 +108,7 @@ SPEC REPOS:
- upnpx
- VLC-LiveSDK
- VLC-WhiteRaccoon
- VLCMediaLibraryKit
- XKKeychain
- xmlrpc
......@@ -165,9 +169,10 @@ SPEC CHECKSUMS:
upnpx: c695b99229e08154d23abe5c252cb64f1600f35d
VLC-LiveSDK: c9566a9edde968f969138f84cfd40b540a109b3f
VLC-WhiteRaccoon: 1e7e59b0568959135a89d09c416d1e11a5d9a986
VLCMediaLibraryKit: 44925062bb94a50e27bf3389a2f7aa4f007f0825
XKKeychain: 852ef663c56a7194c73d3c68e8d9d4f07b121d4f
xmlrpc: 109bb21d15ed6d108b2c1ac5973a6a223a50f5f4
PODFILE CHECKSUM: 0c8231ce51e9fc3523731b5752caab4a1bef5e13
PODFILE CHECKSUM: 94325405e77c452a6048fb7a5a3c81cedf39ded7
COCOAPODS: 1.5.2
COCOAPODS: 1.5.3
/*****************************************************************************
* AlbumModel.swift
*
* Copyright © 2018 VLC authors and VideoLAN
* Copyright © 2018 Videolabs
*
* Authors: Soomin Lee <bubu@mikan.io>
*
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
class AlbumModel: MLBaseModel {
typealias MLType = VLCMLAlbum
var updateView: (() -> Void)?
var files = [VLCMLAlbum]()
var medialibrary: VLCMediaLibraryManager
var indicatorName: String = NSLocalizedString("ALBUMS", comment: "")
required init(medialibrary: VLCMediaLibraryManager) {
self.medialibrary = medialibrary
medialibrary.addObserver(self)
files = medialibrary.getAlbums()
}
func append(_ item: VLCMLAlbum) {
files.append(item)
}
func delete(_ items: [VLCMLObject]) {
preconditionFailure("AlbumModel: Cannot delete album")
}
}
extension AlbumModel: MediaLibraryObserver {
func medialibrary(_ medialibrary: VLCMediaLibraryManager, didAddAlbums albums: [VLCMLAlbum]) {
print("AlbumModel: didAddAlbum: \(albums.count)")
albums.forEach({ append($0) })
updateView?()
}
}
/*****************************************************************************
* ArtistModel.swift
*
* Copyright © 2018 VLC authors and VideoLAN
* Copyright © 2018 Videolabs
*
* Authors: Soomin Lee <bubu@mikan.io>
*
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
class ArtistModel: MLBaseModel {
typealias MLType = VLCMLArtist
var updateView: (() -> Void)?
var files = [VLCMLArtist]()
var medialibrary: VLCMediaLibraryManager
var indicatorName: String = NSLocalizedString("ARTISTS", comment: "")
required init(medialibrary: VLCMediaLibraryManager) {
self.medialibrary = medialibrary
medialibrary.addObserver(self)
files = medialibrary.getArtists()
}
func append(_ item: VLCMLArtist) {
files.append(item)
}
func delete(_ items: [VLCMLObject]) {
preconditionFailure("ArtistModel: Cannot delete artist")
}
}
extension ArtistModel: MediaLibraryObserver {
func medialibrary(_ medialibrary: VLCMediaLibraryManager, didAddArtists artists: [VLCMLArtist]) {
print("ArtistModel: didAddArtists: \(artists.count)")
artists.forEach({ append($0) })
updateView?()
}
}
/*****************************************************************************
* AudioModel.swift
*
* Copyright © 2018 VLC authors and VideoLAN
* Copyright © 2018 Videolabs
*
* Authors: Soomin Lee <bubu@mikan.io>
*
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
class AudioModel: MLBaseModel {
typealias MLType = VLCMLMedia
var updateView: (() -> Void)?
var files = [VLCMLMedia]()
var medialibrary: VLCMediaLibraryManager
var indicatorName: String = NSLocalizedString("SONGS", comment: "")
required init(medialibrary: VLCMediaLibraryManager) {
self.medialibrary = medialibrary
medialibrary.addObserver(self)
files = medialibrary.media(ofType: .audio)
}
func append(_ item: VLCMLMedia) {
if !files.contains { $0 == item } {
files.append(item)
}
}
func delete(_ items: [VLCMLObject]) {
do {
for case let media as VLCMLMedia in items {
try FileManager.default.removeItem(atPath: media.mainFile().mrl.path)
}
medialibrary.reload()
}
catch let error as NSError {
assertionFailure("VideoModel: Delete failed: \(error.localizedDescription)")
}
}
}
// MARK: - Sort
extension AudioModel {
func sort(by criteria: VLCMLSortingCriteria) {
// FIXME: Currently if sorted by name, the files are sorted by filename but displaying title
files = medialibrary.media(ofType: .audio, sortingCriteria: criteria, desc: false)
updateView?()
}
}
// MARK: - MediaLibraryObserver
extension AudioModel: MediaLibraryObserver {
func medialibrary(_ medialibrary: VLCMediaLibraryManager, didAddAudios audios: [VLCMLMedia]) {
print("AudioModel: didAddAudio: \(audios.count)")
audios.forEach({ append($0) })
updateView?()
}
func medialibrary(_ medialibrary: VLCMediaLibraryManager, didDeleteMediaWithIds ids: [NSNumber]) {
print("AudioModel: didDeleteMedia: \(ids)")
files = files.filter() {
for id in ids where $0.identifier() == id.int64Value {
return false
}
return true
}
updateView?()
}
}
/*****************************************************************************
* GenreModel.swift
*
* Copyright © 2018 VLC authors and VideoLAN
* Copyright © 2018 Videolabs
*
* Authors: Soomin Lee <bubu@mikan.io>
*
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
class GenreModel: MLBaseModel {
typealias MLType = VLCMLGenre
var updateView: (() -> Void)?
var files = [VLCMLGenre]()
var medialibrary: VLCMediaLibraryManager
var indicatorName: String = NSLocalizedString("GENRES", comment: "")
required init(medialibrary: VLCMediaLibraryManager) {
self.medialibrary = medialibrary
medialibrary.addObserver(self)
files = medialibrary.genre()
}
func append(_ item: VLCMLGenre) {
files.append(item)
}
func delete(_ items: [VLCMLObject]) {
preconditionFailure("GenreModel: Cannot delete genre")
}
}
extension GenreModel: MediaLibraryObserver {
func medialibrary(_ medialibrary: VLCMediaLibraryManager, didAddGenres genres: [VLCMLGenre]) {
print("GenreModel: didAddGenre: \(genres.count)")
genres.forEach({ append($0) })
updateView?()
}
}
/*****************************************************************************
* MediaLibraryBaseModel.swift
*
* Copyright © 2018 VLC authors and VideoLAN
* Copyright © 2018 Videolabs
*
* Authors: Soomin Lee <bubu@mikan.io>
*
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
// Expose a "shadow" version without associatedType in order to use it as a type
protocol MediaLibraryBaseModel {
init(medialibrary: VLCMediaLibraryManager)
var anyfiles: [VLCMLObject] { get }
var updateView: (() -> Void)? { get set }
var indicatorName: String { get }
func append(_ item: VLCMLObject)
func delete(_ items: [VLCMLObject])
func sort(by criteria: VLCMLSortingCriteria)
}
protocol MLBaseModel: MediaLibraryBaseModel {
associatedtype MLType where MLType: VLCMLObject
init(medialibrary: VLCMediaLibraryManager)
var files: [MLType] { get set }
var medialibrary: VLCMediaLibraryManager { get }
var updateView: (() -> Void)? { get set }
var indicatorName: String { get }
func append(_ item: MLType)
// FIXME: Ideally items should be MLType but Swift isn't happy so it will always fail
func delete(_ items: [VLCMLObject])
func sort(by criteria: VLCMLSortingCriteria)
}
extension MLBaseModel {
var anyfiles: [VLCMLObject] {
return files
}
func append(_ item: VLCMLObject) {
fatalError()
}
func delete(_ items: [VLCMLObject]) {
fatalError()
}
func sort(by criteria: VLCMLSortingCriteria) {
fatalError()
}
}
/*****************************************************************************
* PlaylistModel.swift
*
* Copyright © 2018 VLC authors and VideoLAN
* Copyright © 2018 Videolabs
*
* Authors: Soomin Lee <bubu@mikan.io>
*
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
class PlaylistModel: MLBaseModel {
typealias MLType = VLCMLPlaylist
var updateView: (() -> Void)?
var files = [VLCMLPlaylist]()
var medialibrary: VLCMediaLibraryManager
var indicatorName: String = NSLocalizedString("PLAYLISTS", comment: "")
required init(medialibrary: VLCMediaLibraryManager) {
self.medialibrary = medialibrary
medialibrary.addObserver(self)
files = medialibrary.getPlaylists()
}
func append(_ item: VLCMLPlaylist) {
for file in files {
if file.identifier() == item.identifier() {
return
}
}
files.append(item)
}
func delete(_ items: [VLCMLObject]) {
for playlist in items where playlist is VLCMLPlaylist {
if !(medialibrary.deletePlaylist(with: playlist.identifier())) {
assertionFailure("PlaylistModel: Failed to delete playlist: \(playlist.identifier())")
}
}
}
// Creates a VLCMLPlaylist appending it and updates linked view
func create(name: String) {
append(medialibrary.createPlaylist(with: name))
updateView?()
}
}
extension PlaylistModel: MediaLibraryObserver {
func medialibrary(_ medialibrary: VLCMediaLibraryManager, didAddPlaylists playlists: [VLCMLPlaylist]) {
print("PlaylistModel: didAddPlaylists: \(playlists.count)")
playlists.forEach({ append($0) })
updateView?()
}
func medialibrary(_ medialibrary: VLCMediaLibraryManager, didDeletePlaylistsWithIds playlistsIds: [NSNumber]) {
print("PlaylistModel: didDeletePlaylists: \(playlistsIds)")
files = files.filter() {
for id in playlistsIds where $0.identifier() == id.int64Value {
return false
}
return true
}
updateView?()
}
}
/*****************************************************************************
* ShowEpisodeModel.swift
*
* Copyright © 2018 VLC authors and VideoLAN
* Copyright © 2018 Videolabs
*
* Authors: Soomin Lee <bubu@mikan.io>
*
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
class ShowEpisodeModel: MLBaseModel {
typealias MLType = VLCMLMedia
var updateView: (() -> Void)?
var files = [VLCMLMedia]()
var medialibrary: VLCMediaLibraryManager
var indicatorName: String = NSLocalizedString("EPISODES", comment: "")
required init(medialibrary: VLCMediaLibraryManager) {
self.medialibrary = medialibrary
medialibrary.addObserver(self)
}
func append(_ item: VLCMLMedia) {
files.append(item)
}
func delete(_ items: [VLCMLObject]) {
preconditionFailure("ShowEpisodeModel: Cannot delete showEpisode")
}
}
extension ShowEpisodeModel: MediaLibraryObserver {
func medialibrary(_ medialibrary: VLCMediaLibraryManager, didAddShowEpisodes showEpisodes: [VLCMLMedia]) {
print("ShowEpisode: didAddShowEpisode: \(showEpisodes.count)")
showEpisodes.forEach({ append($0) })
updateView?()
}
}
/*****************************************************************************
* VideoModel.swift
*
* Copyright © 2018 VLC authors and VideoLAN
* Copyright © 2018 Videolabs
*
* Authors: Soomin Lee <bubu@mikan.io>
*
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
class VideoModel: MLBaseModel {
typealias MLType = VLCMLMedia
var updateView: (() -> Void)?
var files = [VLCMLMedia]()
var medialibrary: VLCMediaLibraryManager
var indicatorName: String = NSLocalizedString("MOVIES", comment: "")
required init(medialibrary: VLCMediaLibraryManager) {
self.medialibrary = medialibrary
medialibrary.addObserver(self)
files = medialibrary.media(ofType: .video)
medialibrary.requestThumbnail(for: files)
}
func append(_ item: VLCMLMedia) {
if !files.contains { $0 == item } {
files.append(item)
}
}
func delete(_ items: [VLCMLObject]) {
do {
for case let media as VLCMLMedia in items {
try FileManager.default.removeItem(atPath: media.mainFile().mrl.path)
}
medialibrary.reload()
}
catch let error as NSError {
assertionFailure("VideoModel: Delete failed: \(error.localizedDescription)")
}
}
}
// MARK: - Sort
extension VideoModel {
func sort(by criteria: VLCMLSortingCriteria) {
files = medialibrary.media(ofType: .video, sortingCriteria: criteria, desc: false)
updateView?()
}
}
// MARK: - MediaLibraryObserver
extension VideoModel: MediaLibraryObserver {
func medialibrary(_ medialibrary: VLCMediaLibraryManager, didAddVideos videos: [VLCMLMedia]) {
print("VideoModel: didAddVideo: \(videos.count)")
videos.forEach({ append($0) })
updateView?()
}
func medialibrary(_ medialibrary: VLCMediaLibraryManager, didDeleteMediaWithIds ids: [NSNumber]) {
print("VideoModel: didDeleteMedia: \(ids)")
files = files.filter() {
for id in ids where $0.identifier() == id.int64Value {
return false
}
return true
}
updateView?()
}
}
// MARK: MediaLibraryObserver - Thumbnail
extension VideoModel {
func medialibrary(_ medialibrary: VLCMediaLibraryManager, thumbnailReady media: VLCMLMedia) {
for (index, file) in files.enumerated() {
if file == media {
files[index] = media
break
}
}
updateView?()
}
}
extension VLCMLMedia {
static func == (lhs: VLCMLMedia, rhs: VLCMLMedia) -> Bool {
return lhs.identifier() == rhs.identifier()
}
}
extension VLCMLMedia {
@objc func formatDuration() -> String {
return String(format: "%@", VLCTime(int: Int32(duration())))
}
@objc func formatSize() -> String {
return ByteCountFormatter.string(fromByteCount: Int64(mainFile().size()),
countStyle: .file)
}
}
This diff is collapsed.
......@@ -12,7 +12,7 @@
@objc(VLCService)
class Services: NSObject {
@objc let mediaDataSource = VLCMediaDataSource()
@objc let medialibraryManager = VLCMediaLibraryManager()
@objc let rendererDiscovererManager = VLCRendererDiscovererManager(presentingViewController: nil)
}
......@@ -27,6 +27,10 @@ class Services: NSObject {
self.playerController = VLCPlayerDisplayController(services: services)
super.init()
setupPlayerController()
// Init the HTTP Server and clean its cache
// FIXME: VLCHTTPUploaderController should perhaps be a service?
VLCHTTPUploaderController.sharedInstance().cleanCache()
}
private func setupPlayerController() {
......
......@@ -10,50 +10,82 @@
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
class VLCMovieCategoryViewController: VLCMediaCategoryViewController<MLFile> {
class VLCMovieCategoryViewController: VLCMediaCategoryViewController {
init(_ services: Services) {
super.init(services: services, category: VLCMediaSubcategories.movies)
let model = VideoModel(medialibrary: services.medialibraryManager)
super.init(services: services, category: model)
category.updateView = { [weak self] in
self?.reloadData()
}
}
}
class VLCShowEpisodeCategoryViewController: VLCMediaCategoryViewController<MLShowEpisode> {
class VLCShowEpisodeCategoryViewController: VLCMediaCategoryViewController {
init(_ services: Services) {
super.init(services: services, category: VLCMediaSubcategories.episodes)
let model = ShowEpisodeModel(medialibrary: services.medialibraryManager)
super.init(services: services, category: model)
category.updateView = { [weak self] in
self?.reloadData()
}
}
}
class VLCVideoPlaylistCategoryViewController: VLCMediaCategoryViewController<MLLabel> {
class VLCVideoPlaylistCategoryViewController: VLCMediaCategoryViewController {
init(_ services: Services) {
super.init(services: services, category: VLCMediaSubcategories.videoPlaylists)
let model = PlaylistModel(medialibrary: services.medialibraryManager)
super.init(services: services, category: model)
category.updateView = { [weak self] in
self?.reloadData()
}
}
}
class VLCTrackCategoryViewController: VLCMediaCategoryViewController<MLFile> {
class VLCTrackCategoryViewController: VLCMediaCategoryViewController {
init(_ services: Services) {
super.init(services: services, category: VLCMediaSubcategories.tracks)
let model = AudioModel(medialibrary: services.medialibraryManager)
super.init(services: services, category: model)
category.updateView = { [weak self] in
self?.reloadData()
}
}
}
class VLCGenreCategoryViewController: VLCMediaCategoryViewController<String> {
class VLCGenreCategoryViewController: VLCMediaCategoryViewController {
init(_ services: Services) {
super.init(services: services, category: VLCMediaSubcategories.genres)
let model = GenreModel(medialibrary: services.medialibraryManager)
super.init(services: services, category: model)
category.updateView = { [weak self] in
self?.reloadData()
}
}
}
class VLCArtistCategoryViewController: VLCMediaCategoryViewController<String> {
class VLCArtistCategoryViewController: VLCMediaCategoryViewController {
init(_ services: Services) {
super.init(services: services, category: VLCMediaSubcategories.artists)
let model = ArtistModel(medialibrary: services.medialibraryManager)
super.init(services: services, category: model)
category.updateView = { [weak self] in
self?.reloadData()
}
}
}
class VLCAlbumCategoryViewController: VLCMediaCategoryViewController<MLAlbum> {
class VLCAlbumCategoryViewController: VLCMediaCategoryViewController {
init(_ services: Services) {
super.init(services: services, category: VLCMediaSubcategories.albums)
let model = AlbumModel(medialibrary: services.medialibraryManager)
super.init(services: services, category: model)
category.updateView = { [weak self] in
self?.reloadData()
}
}
}
class VLCAudioPlaylistCategoryViewController: VLCMediaCategoryViewController<MLLabel> {
class VLCAudioPlaylistCategoryViewController: VLCMediaCategoryViewController {
init(_ services: Services) {
super.init(services: services, category: VLCMediaSubcategories.audioPlaylists)
let model = VideoModel(medialibrary: services.medialibraryManager)
super.init(services: services, category: model)
category.updateView = { [weak self] in
self?.reloadData()
}
}
}
......@@ -13,18 +13,18 @@
import Foundation
class VLCMediaCategoryViewController<T>: UICollectionViewController, UICollectionViewDelegateFlowLayout, UISearchResultsUpdating, UISearchControllerDelegate, IndicatorInfoProvider {
class VLCMediaCategoryViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UISearchResultsUpdating, UISearchControllerDelegate, IndicatorInfoProvider {
let cellPadding: CGFloat = 5.0
private var services: Services
private var searchController: UISearchController?
private let searchDataSource = VLCLibrarySearchDisplayDataSource()
var category: VLCMediaSubcategoryModel<T>
var category: MediaLibraryBaseModel
private lazy var editController = VLCEditController(collectionView: self.collectionView!, category: self.category)
@available(iOS 11.0, *)
lazy var dragAndDropManager: VLCDragAndDropManager = { () -> VLCDragAndDropManager<T> in
VLCDragAndDropManager<T>(subcategory: category)
}()
// @available(iOS 11.0, *)
// lazy var dragAndDropManager: VLCDragAndDropManager = { () -> VLCDragAndDropManager<T> in
// VLCDragAndDropManager<T>(subcategory: VLCMediaSubcategories<>)
// }()
lazy var emptyView: VLCEmptyLibraryView = {
let name = String(describing: VLCEmptyLibraryView.self)
......@@ -33,18 +33,23 @@ class VLCMediaCategoryViewController<T>: UICollectionViewController, UICollectio
return emptyView
}()
let editCollectionViewLayout: UICollectionViewFlowLayout = {
let editCollectionViewLayout = UICollectionViewFlowLayout()
editCollectionViewLayout.minimumLineSpacing = 1
editCollectionViewLayout.minimumInteritemSpacing = 0
return editCollectionViewLayout
}()
@available(*, unavailable)
init() {
fatalError()
}
init(services: Services, category: VLCMediaSubcategoryModel<T>) {
init(services: Services, category: MediaLibraryBaseModel) {
self.services = services
self.category = category
super.init(collectionViewLayout: UICollectionViewFlowLayout())
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .VLCThemeDidChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(reloadData), name: category.changeNotificationName, object: nil)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
......@@ -52,8 +57,11 @@ class VLCMediaCategoryViewController<T>: UICollectionViewController, UICollectio
}
@objc func reloadData() {
collectionView?.reloadData()
updateUIForContent()
DispatchQueue.main.async {
[weak self] in
self?.collectionView?.reloadData()
self?.updateUIForContent()
}
}
@available(*, unavailable)
......@@ -86,11 +94,12 @@ class VLCMediaCategoryViewController<T>: UICollectionViewController, UICollectio
func setupCollectionView() {
let playlistnib = UINib(nibName: "VLCPlaylistCollectionViewCell", bundle: nil)
collectionView?.register(playlistnib, forCellWithReuseIdentifier: VLCPlaylistCollectionViewCell.cellIdentifier())
collectionView?.register(VLCMediaViewEditCell.self, forCellWithReuseIdentifier: VLCMediaViewEditCell.identifier)
collectionView?.backgroundColor = PresentationTheme.current.colors.background
collectionView?.alwaysBounceVertical = true
if #available(iOS 11.0, *) {
collectionView?.dragDelegate = dragAndDropManager
collectionView?.dropDelegate = dragAndDropManager
// collectionView?.dragDelegate = dragAndDropManager
// collectionView?.dropDelegate = dragAndDropManager
}
}
......@@ -134,10 +143,32 @@ class VLCMediaCategoryViewController<T>: UICollectionViewController, UICollectio
collectionView?.collectionViewLayout.invalidateLayout()
}
// MARK: - Edit
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
// might have an issue if the old datasource was search
// Most of the edit logic is handled inside editController
collectionView?.dataSource = editing ? editController : self
collectionView?.delegate = editing ? editController : self
editController.toolbarNeedsUpdate(editing: editing)
let layoutToBe = editing ? editCollectionViewLayout : UICollectionViewFlowLayout()
collectionView?.setCollectionViewLayout(layoutToBe, animated: false, completion: {
[weak self] finished in
guard finished else {
assertionFailure("VLCMediaSubcategoryViewController: Edit layout transition failed.")
return
}
self?.reloadData()
})
}
// MARK: - Search
func updateSearchResults(for searchController: UISearchController) {
searchDataSource.shouldReloadTable(forSearch: searchController.searchBar.text, searchableFiles: category.files)
searchDataSource.shouldReloadTable(forSearch: searchController.searchBar.text, searchableFiles: category.anyfiles)
collectionView?.reloadData()
}
......@@ -150,19 +181,23 @@ class VLCMediaCategoryViewController<T>: UICollectionViewController, UICollectio
}
func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
return IndicatorInfo(title:category.indicatorInfoName)
return IndicatorInfo(title:category.indicatorName)
}
// MARK: - UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return category.files.count
return category.anyfiles.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let playlistCell = collectionView.dequeueReusableCell(withReuseIdentifier: VLCPlaylistCollectionViewCell.cellIdentifier(), for: indexPath) as? VLCPlaylistCollectionViewCell {
if let mediaObject = category.files[indexPath.row] as? NSManagedObject {
if let mediaObject = category.anyfiles[indexPath.row] as? NSManagedObject {
playlistCell.mediaObject = mediaObject
}
if let media = category.anyfiles[indexPath.row] as? VLCMLMedia {
playlistCell.media = (media.mainFile() != nil) ? media : nil
}
return playlistCell
}
return UICollectionViewCell()
......@@ -170,8 +205,10 @@ class VLCMediaCategoryViewController<T>: UICollectionViewController, UICollectio
// MARK: - UICollectionViewDelegate
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let mediaObject = category.files[indexPath.row] as? NSManagedObject {
if let mediaObject = category.anyfiles[indexPath.row] as? NSManagedObject {
play(mediaObject: mediaObject)
} else if let media = category.anyfiles[indexPath.row] as? VLCMLMedia {
play(media: media)
}
}
......@@ -203,10 +240,40 @@ class VLCMediaCategoryViewController<T>: UICollectionViewController, UICollectio
}
}
// MARK: - Sort
extension VLCMediaCategoryViewController {
// FIXME: Need to add a button for ascending/descending result
func sortByFileName() {
// The issue is that for audio we show title which is quite confusing if we use filename
category.sort(by: .alpha)
}
func sortByDate() {
category.sort(by: .insertionDate)
}
func sortBySize() {
category.sort(by: .fileSize)
}
}
// MARK: - Player
extension VLCMediaCategoryViewController {
func play(mediaObject: NSManagedObject) {
VLCPlaybackController.sharedInstance().playMediaLibraryObject(mediaObject)
}
func play(media: VLCMLMedia) {
VLCPlaybackController.sharedInstance().play(media)
}
}
// MARK: - MediaLibraryModelView
extension VLCMediaCategoryViewController {
func dataChanged() {
reloadData()
}
}
/*****************************************************************************
* VLCMediaSubcategory.swift
* VLC for iOS
*****************************************************************************
* Copyright (c) 2017 VideoLAN. All rights reserved.
* $Id$
*
* Authors: Carola Nitz <caro # videolan.org>
*
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
import Foundation
extension Notification.Name {
static let VLCMoviesDidChangeNotification = Notification.Name("MoviesDidChangeNotfication")
static let VLCEpisodesDidChangeNotification = Notification.Name("EpisodesDidChangeNotfication")
static let VLCArtistsDidChangeNotification = Notification.Name("ArtistsDidChangeNotfication")
static let VLCAlbumsDidChangeNotification = Notification.Name("AlbumsDidChangeNotfication")
static let VLCTracksDidChangeNotification = Notification.Name("TracksDidChangeNotfication")
static let VLCGenresDidChangeNotification = Notification.Name("GenresDidChangeNotfication")
static let VLCAudioPlaylistsDidChangeNotification = Notification.Name("AudioPlaylistsDidChangeNotfication")
static let VLCVideoPlaylistsDidChangeNotification = Notification.Name("VideoPlaylistsDidChangeNotfication")
static let VLCVideosDidChangeNotification = Notification.Name("VideosDidChangeNotfication")
}
enum VLCDataUnit {
case file(MLFile)
case episode(MLShowEpisode)
case album(MLAlbum)
case label(MLLabel)
}
class VLCMediaSubcategoryModel<T>: NSObject {
var files: [T]
var indicatorInfoName: String
var changeNotificationName: Notification.Name
var includesFunc: (VLCDataUnit) -> Bool
var appendFunc: (VLCDataUnit) -> Void
var indicatorInfo: IndicatorInfo {
return IndicatorInfo(title: indicatorInfoName)
}
init(files: [T],
indicatorInfoName: String,
notificationName: Notification.Name,
includesFunc: @escaping (VLCDataUnit) -> Bool,
appendFunc: @escaping (VLCDataUnit) -> Void) {
self.files = files
self.indicatorInfoName = indicatorInfoName
self.changeNotificationName = notificationName
self.includesFunc = includesFunc
self.appendFunc = appendFunc
}
}
struct VLCMediaSubcategories {
static var movies = VLCMediaSubcategoryModel<MLFile>(
files: {
(MLFile.allFiles() as! [MLFile]).filter {
($0 as MLFile).isKind(ofType: kMLFileTypeMovie) ||
($0 as MLFile).isKind(ofType: kMLFileTypeTVShowEpisode) ||
($0 as MLFile).isKind(ofType: kMLFileTypeClip)
}
}(),
indicatorInfoName: NSLocalizedString("MOVIES", comment: ""),
notificationName: .VLCMoviesDidChangeNotification,
includesFunc: { (dataUnit: VLCDataUnit) in
if case .file(let f) = dataUnit {
return f.isMovie()
}
return false
},
appendFunc: { (dataUnit: VLCDataUnit) in
})
static var episodes = VLCMediaSubcategoryModel<MLShowEpisode>(
files: MLShowEpisode.allEpisodes() as! [MLShowEpisode],
indicatorInfoName: NSLocalizedString("EPISODES", comment: ""),
notificationName: .VLCEpisodesDidChangeNotification,
includesFunc: { (dataUnit: VLCDataUnit) in
if case .episode(let f) = dataUnit {
return true
}
return false
},
appendFunc: { (dataUnit: VLCDataUnit) in
})
static var artists = VLCMediaSubcategoryModel<String>(
files: {
let tracksWithArtist = (MLAlbumTrack.allTracks() as! [MLAlbumTrack]).filter { $0.artist != nil && $0.artist != "" }
return tracksWithArtist.map { $0.artist } as! [String]
}(),
indicatorInfoName: NSLocalizedString("ARTISTS", comment: ""),
notificationName: .VLCArtistsDidChangeNotification,
includesFunc: { (dataUnit: VLCDataUnit) in
if case .file(let f) = dataUnit {
return f.artist != nil
}
return false
},
appendFunc: { (dataUnit: VLCDataUnit) in
})
static var albums = VLCMediaSubcategoryModel<MLAlbum>(
files: MLAlbum.allAlbums() as! [MLAlbum],
indicatorInfoName: NSLocalizedString("ALBUMS", comment: ""),
notificationName: .VLCAlbumsDidChangeNotification,
includesFunc: { (dataUnit: VLCDataUnit) in
if case .album(let f) = dataUnit {
return true
}
return false
},
appendFunc: { (dataUnit: VLCDataUnit) in
})
static var tracks = VLCMediaSubcategoryModel<MLFile>(
files: (MLFile.allFiles() as! [MLFile]).filter { $0.isSupportedAudioFile()},
indicatorInfoName: NSLocalizedString("SONGS", comment: ""),
notificationName: .VLCTracksDidChangeNotification,
includesFunc: { (dataUnit: VLCDataUnit) in
if case .file(let f) = dataUnit {
return f.isSupportedAudioFile()
}
return false
},
appendFunc: { (dataUnit: VLCDataUnit) in
})
static var genres = VLCMediaSubcategoryModel<String>(
files: {
let albumtracks = MLAlbumTrack.allTracks() as! [MLAlbumTrack]
let tracksWithArtist = albumtracks.filter { $0.genre != nil && $0.genre != "" }
return tracksWithArtist.map { $0.genre }
}(),
indicatorInfoName: NSLocalizedString("GENRES", comment: ""),
notificationName: .VLCGenresDidChangeNotification ,
includesFunc: { (dataUnit: VLCDataUnit) in
if case .file(let f) = dataUnit {
return f.genre != nil
}
return false
},
appendFunc: { (dataUnit: VLCDataUnit) in
})
static var audioPlaylists = VLCMediaSubcategoryModel<MLLabel>(
files: {
let labels = MLLabel.allLabels() as! [MLLabel]
let audioPlaylist = labels.filter {
let audioFiles = $0.files.filter {
if let file = $0 as? MLFile {
return file.isSupportedAudioFile()
}
return false
}
return !audioFiles.isEmpty
}
return audioPlaylist
}(),
indicatorInfoName: NSLocalizedString("AUDIO_PLAYLISTS", comment: ""),
notificationName: .VLCAudioPlaylistsDidChangeNotification ,
includesFunc: { (dataUnit: VLCDataUnit) in
if case .label(let l) = dataUnit {
let audioFiles = l.files.filter {
if let file = $0 as? MLFile {
return file.isSupportedAudioFile()
} else {
return false
}
}
return !audioFiles.isEmpty
}
return false
},
appendFunc: { (dataUnit: VLCDataUnit) in
})
static var videoPlaylists = VLCMediaSubcategoryModel<MLLabel>(
files: {
let labels = MLLabel.allLabels() as! [MLLabel]
let audioPlaylist = labels.filter {
let audioFiles = $0.files.filter {
if let file = $0 as? MLFile {
return file.isShowEpisode() || file.isMovie() || file.isClip()
}
return false
}
return !audioFiles.isEmpty
}
return audioPlaylist
}(),
indicatorInfoName: NSLocalizedString("VIDEO_PLAYLISTS", comment: ""),
notificationName: .VLCVideoPlaylistsDidChangeNotification ,
includesFunc: { (dataUnit: VLCDataUnit) in
if case .label(let l) = dataUnit {
let videoFiles = l.files.filter {
if let file = $0 as? MLFile {
return file.isShowEpisode() || file.isMovie() || file.isClip()
} else {
return false
}
}
return !videoFiles.isEmpty
}
return false
},
appendFunc: { (dataUnit: VLCDataUnit) in
})
}
......@@ -15,6 +15,7 @@ import UIKit
class VLCMediaViewController: VLCPagingViewController<VLCLabelCell> {
var services: Services
private var rendererButton: UIButton
private let fixedSpaceWidth: CGFloat = 21
init(services: Services) {
self.services = services
......@@ -37,18 +38,39 @@ class VLCMediaViewController: VLCPagingViewController<VLCLabelCell> {
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = false
}
let fixedSpace: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
fixedSpace.width = fixedSpaceWidth
navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("SORT", comment: ""), style: .plain, target: self, action: #selector(sort))
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: rendererButton)
navigationItem.rightBarButtonItems = [editButtonItem, fixedSpace, UIBarButtonItem(customView: rendererButton)]
}
@objc func sort() {
// This should be in a subclass
let sortOptionsAlertController = UIAlertController(title: NSLocalizedString("SORT_BY", comment: ""), message: nil, preferredStyle: .actionSheet)
let sortByNameAction = UIAlertAction(title: SortOption.alphabetically.localizedDescription, style: .default) { action in
let sortByNameAction = UIAlertAction(title: SortOption.alphabetically.localizedDescription, style: .default) {
[weak self] action in
// call medialibrary
if let index = self?.currentIndex {
let currentViewController = self?.viewControllers[index] as? VLCMediaCategoryViewController
currentViewController?.sortByFileName()
}
}
let sortBySizeAction = UIAlertAction(title: SortOption.size.localizedDescription, style: .default) { action in
let sortBySizeAction = UIAlertAction(title: SortOption.size.localizedDescription, style: .default) {
[weak self] action in
if let index = self?.currentIndex {
let currentViewController = self?.viewControllers[index] as? VLCMediaCategoryViewController
currentViewController?.sortBySize()
}
}
let sortbyDateAction = UIAlertAction(title: SortOption.insertonDate.localizedDescription, style: .default) { action in
let sortbyDateAction = UIAlertAction(title: SortOption.insertonDate.localizedDescription, style: .default) {
[weak self] action in
if let index = self?.currentIndex {
let currentViewController = self?.viewControllers[index] as? VLCMediaCategoryViewController
currentViewController?.sortByDate()
}
}
let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL", comment: ""), style: .cancel, handler: nil)
sortOptionsAlertController.addAction(sortByNameAction)
......@@ -56,6 +78,7 @@ class VLCMediaViewController: VLCPagingViewController<VLCLabelCell> {
sortOptionsAlertController.addAction(sortBySizeAction)
sortOptionsAlertController.addAction(cancelAction)
sortOptionsAlertController.view.tintColor = UIColor.vlcOrangeTint()
sortOptionsAlertController.popoverPresentationController?.sourceView = self.view
present(sortOptionsAlertController, animated: true)
}
......@@ -76,4 +99,9 @@ class VLCMediaViewController: VLCPagingViewController<VLCLabelCell> {
override var preferredStatusBarStyle: UIStatusBarStyle {
return PresentationTheme.current.colors.statusBarStyle
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
viewControllers[currentIndex].setEditing(editing, animated: animated)
}
}
......@@ -97,9 +97,6 @@
[VLCApperanceManager setupAppearanceWithTheme:PresentationTheme.current];
// Init the HTTP Server and clean its cache
[[VLCHTTPUploaderController sharedInstance] cleanCache];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
rootViewController = [UITabBarController new];
self.window.rootViewController = rootViewController;
......
......@@ -302,6 +302,9 @@
APLog(@"BoxFile download was successful");
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, NSLocalizedString(@"GDRIVE_DOWNLOAD_SUCCESSFUL", nil));
[[VLCMediaFileDiscoverer sharedInstance] performSelectorOnMainThread:@selector(updateMediaList) withObject:nil waitUntilDone:NO];
// FIXME: Replace notifications by cleaner observers
[[NSNotificationCenter defaultCenter] postNotificationName:NSNotification.VLCNewFileAddedNotification
object:self];
if ([self.delegate respondsToSelector:@selector(operationWithProgressInformationStopped)])
[self.delegate operationWithProgressInformationStopped];
......
This diff is collapsed.
......@@ -17,6 +17,7 @@
#import "VLCActivityManager.h"
#import "VLCMediaFileDiscoverer.h"
#import "VLCDropboxConstants.h"
#import "VLC_iOS-Swift.h"
@interface VLCDropboxController ()
{
......@@ -236,7 +237,9 @@
if ([self.delegate respondsToSelector:@selector(operationWithProgressInformationStopped)]) {
[self.delegate operationWithProgressInformationStopped];
}
// FIXME: Replace notifications by cleaner observers
[[NSNotificationCenter defaultCenter] postNotificationName:NSNotification.VLCNewFileAddedNotification
object:self];
self->_downloadInProgress = NO;
[self _triggerNextDownload];
if (networkError) {
......
/*****************************************************************************
* VLCEditController.swift
*
* Copyright © 2018 VLC authors and VideoLAN
* Copyright © 2018 Videolabs
*
* Authors: Soomin Lee <bubu@mikan.io>
*
* Refer to the COPYING file of the official project for license.
*****************************************************************************/
protocol VLCEditControllerDataSource {
func toolbarNeedsUpdate(editing: Bool)
}
class VLCEditController: NSObject {
private var selectedCellIndexPaths = Set<IndexPath>()
private let collectionView: UICollectionView
private let category: MediaLibraryBaseModel
// private lazy var editToolbar: VLCEditToolbar = {
// let editToolbar = VLCEditToolbar(frame: CGRect(x: 0, y: 400,
// width: collectionView.frame.width, height: 50))
// editToolbar.isHidden = true
// editToolbar.delegate = self
// return editToolbar
// }()
init(collectionView: UICollectionView, category: MediaLibraryBaseModel) {
self.collectionView = collectionView
self.category = category
super.init()
// collectionView.addSubview(editToolbar)
// collectionView.bringSubview(toFront: editToolbar)
}
}
// MARK: - Helpers
private extension VLCEditController {
private func resetCell(at indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? VLCMediaViewEditCell {
cell.checkView.isEnabled = false
}
collectionView.reloadData()
}
private func resetAllVisibleCell() {
for case let cell as VLCMediaViewEditCell in collectionView.visibleCells {
cell.checkView.isEnabled = false
}
}
private struct TextFieldAlertInfo {
var alertTitle: String
var placeHolder: String
var confirmActionTitle: String
init(alertTitle: String = "",
placeHolder: String = "",
confirmActionTitle: String = NSLocalizedString("BUTTON_DONE", comment: "")) {
self.alertTitle = alertTitle
self.placeHolder = placeHolder
self.confirmActionTitle = confirmActionTitle
}
}
private func presentTextFieldAlert(with info: TextFieldAlertInfo,
completionHandler: @escaping (String) -> Void) {
let alertController = UIAlertController(title: info.alertTitle,
message: "",
preferredStyle: .alert)
alertController.addTextField(configurationHandler: {
textField in
textField.placeholder = info.placeHolder
})
let cancelButton = UIAlertAction(title: NSLocalizedString("BUTTON_CANCEL", comment: ""),
style: .default)
let confirmAction = UIAlertAction(title: info.confirmActionTitle, style: .default) {
[weak alertController] _ in
guard let alertController = alertController,
let textField = alertController.textFields?.first else { return }
completionHandler(textField.text ?? "")
}
alertController.addAction(cancelButton)
alertController.addAction(confirmAction)
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
}
}
// MARK: - VLCEditControllerDataSource
extension VLCEditController: VLCEditControllerDataSource {
func toolbarNeedsUpdate(editing: Bool) {
// editToolbar.isHidden = !editing
if !editing {
// not in editing mode anymore should reset
selectedCellIndexPaths.removeAll(keepingCapacity: false)
}
}
}
// MARK: - VLCEditToolbarDelegate
extension VLCEditController: VLCEditToolbarDelegate {
func createPlaylist() {
if let model = category as? PlaylistModel {
let alertInfo = TextFieldAlertInfo(alertTitle: NSLocalizedString("VIDEO_PLAYLISTS", comment: ""),
placeHolder: "NEW_PLAYLIST")
presentTextFieldAlert(with: alertInfo, completionHandler: {
text -> Void in
model.create(name: text)
})
} else if let model = category as? VideoModel {
let alertInfo = TextFieldAlertInfo(alertTitle: NSLocalizedString("VIDEO_PLAYLISTS", comment: ""),
placeHolder: "NEW_PLAYLIST")
presentTextFieldAlert(with: alertInfo, completionHandler: {
[selectedCellIndexPaths, category] text -> Void in
let playlist = model.medialibrary.createPlaylist(with: text)
for indexPath in selectedCellIndexPaths {
if let media = category.anyfiles[indexPath.row] as? VLCMLMedia {
playlist.appendMedia(withIdentifier: media.identifier())
}
}
})
}
}
func delete() {
var objectsToDelete = [VLCMLObject]()
for indexPath in selectedCellIndexPaths {
objectsToDelete.append(category.anyfiles[indexPath.row])
}
let cancelButton = VLCAlertButton(title: NSLocalizedString("BUTTON_CANCEL", comment: ""))
let deleteButton = VLCAlertButton(title: NSLocalizedString("BUTTON_DELETE", comment: ""),
action: {
[weak self] action in
self?.category.delete(objectsToDelete)
self?.selectedCellIndexPaths.removeAll()
self?.resetAllVisibleCell()
})
VLCAlertViewController.alertViewManager(title: NSLocalizedString("DELETE_TITLE", comment: ""),
errorMessage: NSLocalizedString("DELETE_MESSAGE", comment: ""),
viewController: (UIApplication.shared.keyWindow?.rootViewController)!,
buttonsAction: [cancelButton,
deleteButton])
}
func rename() {
// FIXME: Multiple renaming of files(multiple alert can get unfriendly if too many files)
for indexPath in selectedCellIndexPaths {
if let media = category.anyfiles[indexPath.row] as? VLCMLMedia {
// Not using VLCAlertViewController to have more customization in text fields
let alertInfo = TextFieldAlertInfo(alertTitle: String(format: NSLocalizedString("RENAME_MEDIA_TO", comment: ""), media.title),
placeHolder: "NEW_NAME",
confirmActionTitle: NSLocalizedString("BUTTON_RENAME", comment: ""))
presentTextFieldAlert(with: alertInfo, completionHandler: {
[weak self] text -> Void in
guard text != "" else {
VLCAlertViewController.alertViewManager(title: NSLocalizedString("ERROR_RENAME_FAILED", comment: ""),
errorMessage: NSLocalizedString("ERROR_EMPTY_NAME", comment: ""),
viewController: (UIApplication.shared.keyWindow?.rootViewController)!)
return
}
media.updateTitle(text)
self?.resetCell(at: indexPath)
})
}
}
}
}
// MARK: - UICollectionViewDataSource
extension VLCEditController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return category.anyfiles.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: VLCMediaViewEditCell.identifier,
for: indexPath) as? VLCMediaViewEditCell {
if let media = category.anyfiles[indexPath.row] as? VLCMLMedia {
cell.titleLabel.text = media.title
cell.subInfoLabel.text = media.formatDuration()
cell.sizeLabel.text = media.formatSize()
}
return cell
}
return UICollectionViewCell()
}
}
// MARK: - UICollectionViewDelegate
extension VLCEditController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? VLCMediaViewEditCell {
cell.checkView.isEnabled = !cell.checkView.isEnabled
if cell.checkView.isEnabled {
// cell selected, saving indexPath
selectedCellIndexPaths.insert(indexPath)
} else {
selectedCellIndexPaths.remove(indexPath)
}
}
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension VLCEditController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let contentInset = collectionView.contentInset
// FIXME: 5 should be cell padding, but not usable maybe static?
let insetToRemove = contentInset.left + contentInset.right + (5 * 2)
return CGSize(width: collectionView.frame.width - insetToRemove, height: VLCMediaViewEditCell.height)
}
}
/*****************************************************************************