Browse Source

VideoGroup: Initial

Closes #724
Soomin Lee 5 years ago
parent
commit
1832fa2dfb

+ 1 - 0
Resources/en.lproj/Localizable.strings

@@ -316,6 +316,7 @@
 "MOVIES" = "Movies";
 "SONGS" = "Songs";
 "PLAYLISTS" = "Playlists";
+"VIDEO_GROUPS" = "Video groups";
 
 "MODIFIED_DATE" = "Modified date";
 "NAME" = "Name";

+ 119 - 0
SharedSources/MediaLibraryModel/VideoGroupViewModel.swift

@@ -0,0 +1,119 @@
+/*****************************************************************************
+ * VideoGroupViewModel.swift
+ *
+ * Copyright © 2019 VLC authors and VideoLAN
+ *
+ * Authors: Soomin Lee <bubu@mikan.io>
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+// Fake VLCMLVideoGroup as a medialibrary entity with an id
+
+extension VLCMLVideoGroup: VLCMLObject {
+    public func identifier() -> VLCMLIdentifier {
+        return 42
+    }
+}
+
+class VideoGroupViewModel: MLBaseModel {
+    typealias MLType = VLCMLVideoGroup
+
+    var sortModel = SortModel([.alpha, .nbVideo])
+
+    var updateView: (() -> Void)?
+
+    var files: [VLCMLVideoGroup]
+
+    var cellType: BaseCollectionViewCell.Type { return MovieCollectionViewCell.self }
+
+    var medialibrary: MediaLibraryService
+
+    var indicatorName: String = NSLocalizedString("VIDEO_GROUPS", comment: "")
+
+    required init(medialibrary: MediaLibraryService) {
+        self.medialibrary = medialibrary
+        self.files = medialibrary.medialib.videoGroups() ?? []
+        medialibrary.medialib.setVideoGroupsAllowSingleVideo(false)
+    }
+
+    func append(_ item: VLCMLVideoGroup) {
+        assertionFailure("VideoGroupViewModel: Cannot append VideoGroups")
+    }
+
+    func delete(_ items: [VLCMLObject]) {
+        assertionFailure("VideoGroupViewModel: Cannot delete VideoGroups")
+    }
+
+    func updateVideoGroups() {
+        files = medialibrary.medialib.videoGroups() ?? []
+    }
+
+    func sort(by criteria: VLCMLSortingCriteria, desc: Bool) {
+        files = medialibrary.medialib.videoGroups(with: criteria, desc: desc) ?? []
+        sortModel.currentSort = criteria
+        sortModel.desc = desc
+        updateView?()
+    }
+}
+
+// MARK: - Edit
+
+extension VideoGroupViewModel: EditableMLModel {
+    func editCellType() -> BaseCollectionViewCell.Type {
+        return MediaEditCell.self
+    }
+}
+
+// MARK: - VLCMLVideoGroup - Search
+
+extension VLCMLVideoGroup: SearchableMLModel {
+    func contains(_ searchString: String) -> Bool {
+        return name().lowercased().contains(searchString)
+    }
+}
+
+// MARK: - VLCMLVideoGroup - MediaCollectionModel
+
+extension VLCMLVideoGroup: MediaCollectionModel {
+    func sortModel() -> SortModel? {
+        return nil
+    }
+
+    func files() -> [VLCMLMedia]? {
+        return media()
+    }
+
+    func title() -> String {
+        return name()
+    }
+}
+
+// MARK: - VLCMLVideoGroup - Helpers
+
+extension VLCMLVideoGroup {
+    func numberOfTracksString() -> String {
+        let mediaCount = count()
+        let tracksString = mediaCount > 1 ? NSLocalizedString("TRACKS", comment: "") : NSLocalizedString("TRACK", comment: "")
+        return String(format: tracksString, mediaCount)
+    }
+
+    @objc func thumbnail() -> UIImage? {
+        var image: UIImage?
+
+        for media in files() ?? [] where media.isThumbnailGenerated() {
+            image = UIImage(contentsOfFile: media.thumbnail()?.path ?? "")
+            break
+        }
+
+        if image == nil {
+            let isDarktheme = PresentationTheme.current == PresentationTheme.darkTheme
+            image = isDarktheme ? UIImage(named: "movie-placeholder-dark") : UIImage(named: "movie-placeholder-white")
+        }
+        return image
+    }
+
+    func accessibilityText() -> String? {
+        return name() + " " + numberOfTracksString()
+    }
+}

+ 7 - 2
Sources/EditToolbar.swift

@@ -80,13 +80,18 @@ class EditToolbar: UIView {
     }
 
     private func setupStackView() {
-        let stackView = UIStackView(arrangedSubviews: [addToPlaylistButton, deleteButton])
+        let stackView = UIStackView(arrangedSubviews: [addToPlaylistButton])
         let file = category.anyfiles.first
 
-        if !(file is VLCMLArtist) && !(file is VLCMLGenre) && !(file is VLCMLAlbum) {
+        if !(file is VLCMLArtist) && !(file is VLCMLGenre) && !(file is VLCMLAlbum)
+            && !(file is VLCMLVideoGroup) {
             stackView.addArrangedSubview(renameButton)
         }
 
+        if  !(file is VLCMLVideoGroup) {
+            stackView.addArrangedSubview(deleteButton)
+        }
+
         stackView.addArrangedSubview(shareButton)
 
         stackView.translatesAutoresizingMaskIntoConstraints = false

+ 10 - 0
Sources/MediaCategories/MediaCategory.swift

@@ -20,6 +20,16 @@ class MovieCategoryViewController: MediaCategoryViewController {
     }
 }
 
+class VideoGroupCategoryViewController: MediaCategoryViewController {
+    init(_ services: Services) {
+        let model = VideoGroupViewModel(medialibrary: services.medialibraryService)
+        super.init(services: services, model: model)
+        model.updateView = { [weak self] in
+            self?.reloadData()
+        }
+    }
+}
+
 class ShowEpisodeCategoryViewController: MediaCategoryViewController {
     init(_ services: Services) {
         let model = ShowEpisodeModel(medialibrary: services.medialibraryService)

+ 8 - 0
Sources/MediaCategories/MediaCategoryViewController.swift

@@ -147,6 +147,13 @@ class MediaCategoryViewController: UICollectionViewController, UISearchBarDelega
         }
     }
 
+    private func updateVideoGroups() {
+        // Manually update video groups since there is no callbacks for it
+        if let videoGroupViewModel = model as? VideoGroupViewModel {
+            videoGroupViewModel.updateVideoGroups()
+        }
+    }
+
     @objc func reloadData() {
         DispatchQueue.main.async {
             [weak self] in
@@ -189,6 +196,7 @@ class MediaCategoryViewController: UICollectionViewController, UISearchBarDelega
         manager.presentingViewController = self
         cachedCellSize = .zero
         collectionView.collectionViewLayout.invalidateLayout()
+        updateVideoGroups()
         reloadData()
     }
 

+ 11 - 0
Sources/MediaCategoryCells/MovieCollectionViewCell.swift

@@ -34,6 +34,8 @@ class MovieCollectionViewCell: BaseCollectionViewCell {
                 update(movie:movie)
             } else if let playlist = media as? VLCMLPlaylist {
                 update(playlist:playlist)
+            } else if let videoGroup = media as? VLCMLVideoGroup {
+                update(videoGroup: videoGroup)
             } else {
                 assertionFailure("MovieCollectionViewCell: media: Needs to be of a supported Type.")
             }
@@ -85,6 +87,15 @@ class MovieCollectionViewCell: BaseCollectionViewCell {
         thumbnailView.image = playlist.thumbnail()
     }
 
+    func update(videoGroup: VLCMLVideoGroup) {
+        collectionOverlay.isHidden = false
+        numberOfTracks.text = String(videoGroup.count())
+        titleLabel.text = videoGroup.name()
+        accessibilityLabel = videoGroup.accessibilityText()
+        descriptionLabel.text = videoGroup.numberOfTracksString()
+        thumbnailView.image = videoGroup.thumbnail()
+    }
+
     override class func numberOfColumns(for width: CGFloat) -> CGFloat {
         if width <= DeviceWidth.iPhonePortrait.rawValue {
             return 2

+ 12 - 0
Sources/MediaEditCell.swift

@@ -39,6 +39,8 @@ class MediaEditCell: BaseCollectionViewCell {
                 updateForGenre(genre: genre)
             } else if let playlist = media as? VLCMLPlaylist {
                 updateForPlaylist(playlist: playlist)
+            } else if let videoGroup = media as? VLCMLVideoGroup {
+                update(videoGroup: videoGroup)
             } else {
                 assertionFailure("MediaEditCell: media: Needs to be of a supported Type.")
             }
@@ -120,6 +122,16 @@ class MediaEditCell: BaseCollectionViewCell {
         thumbnailImageView.image = playlist.thumbnail()
     }
 
+    func update(videoGroup: VLCMLVideoGroup) {
+        thumbnailImageView.layer.cornerRadius = 3
+        AudioAspectRatio.isActive = false
+        VideoAspectRatio.isActive = true
+        titleLabel.text = videoGroup.name()
+        accessibilityLabel = videoGroup.accessibilityText()
+        timeLabel.text = videoGroup.numberOfTracksString()
+        thumbnailImageView.image = videoGroup.thumbnail()
+    }
+
     var isChecked: Bool = false {
          didSet {
             checkboxImageView.image = isChecked ? UIImage(named: "checkboxSelected") : UIImage(named: "checkboxEmpty")

+ 2 - 1
Sources/MediaViewControllers/VideoViewController.swift

@@ -26,6 +26,7 @@ class VLCVideoViewController: VLCMediaViewController {
     }
 
     override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
-        return [MovieCategoryViewController(services)]
+        return [MovieCategoryViewController(services),
+                VideoGroupCategoryViewController(services)]
     }
 }

+ 4 - 0
VLC.xcodeproj/project.pbxproj

@@ -254,6 +254,7 @@
 		8D4F9B472141630000E478BE /* MediaModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D4F9B462141630000E478BE /* MediaModel.swift */; };
 		8D66A47320AC61B900FA5B92 /* MediaLibraryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D66A47220AC61B900FA5B92 /* MediaLibraryService.swift */; };
 		8D6E1588223BBAF600077DD3 /* MiniPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D6E1587223BBAF600077DD3 /* MiniPlayer.swift */; };
+		8D83219B234256A900B28912 /* VideoGroupViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D83219A234256A900B28912 /* VideoGroupViewModel.swift */; };
 		8D96D2F2233A3D4000E3FB44 /* CustomDialogRendererHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 418DFE9E211C93C6005D3652 /* CustomDialogRendererHandler.swift */; };
 		8DD6516F208C89BC0052EE68 /* VLCAccessibilityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DD6516E208C89BC0052EE68 /* VLCAccessibilityIdentifier.swift */; };
 		8DD651BA208F6AF00052EE68 /* ActionSheetCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DD651B9208F6AF00052EE68 /* ActionSheetCell.swift */; };
@@ -833,6 +834,7 @@
 		8D4F9B462141630000E478BE /* MediaModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaModel.swift; sourceTree = "<group>"; };
 		8D66A47220AC61B900FA5B92 /* MediaLibraryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLibraryService.swift; sourceTree = "<group>"; };
 		8D6E1587223BBAF600077DD3 /* MiniPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniPlayer.swift; sourceTree = "<group>"; };
+		8D83219A234256A900B28912 /* VideoGroupViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoGroupViewModel.swift; sourceTree = "<group>"; };
 		8DD6516E208C89BC0052EE68 /* VLCAccessibilityIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCAccessibilityIdentifier.swift; sourceTree = "<group>"; };
 		8DD651B9208F6AF00052EE68 /* ActionSheetCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSheetCell.swift; sourceTree = "<group>"; };
 		8DD651C3208F786F0052EE68 /* ActionSheetSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSheetSectionHeader.swift; sourceTree = "<group>"; };
@@ -1962,6 +1964,7 @@
 				8DF966EE211C643D00D0FCD6 /* PlaylistModel.swift */,
 				8D4F9B462141630000E478BE /* MediaModel.swift */,
 				4120863922323E87006A6630 /* CollectionModel.swift */,
+				8D83219A234256A900B28912 /* VideoGroupViewModel.swift */,
 			);
 			path = MediaLibraryModel;
 			sourceTree = "<group>";
@@ -2971,6 +2974,7 @@
 				E0539E8C22E5E2EC009317CB /* VideoOptionsControlBar.swift in Sources */,
 				D6E034ED1CC284FC0037F516 /* VLCStreamingHistoryCell.m in Sources */,
 				DD3EFEED1BDEBA3800B68579 /* VLCNetworkServerBrowserViewController.m in Sources */,
+				8D83219B234256A900B28912 /* VideoGroupViewModel.swift in Sources */,
 				8DF9669D2113317E00D0FCD6 /* EditToolbar.swift in Sources */,
 				416443862048419E00CAC646 /* DeviceMotion.swift in Sources */,
 				7D9289751877459B009108FD /* VLCFirstStepsWifiSharingViewController.m in Sources */,