Browse Source

MediaMoreOptionsActionSheet: Centralize MoreOptionsActionSheet for VLCMovieViewController

Robert Gordon 6 years ago
parent
commit
d59277d783

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

@@ -378,3 +378,7 @@
 "MORE_OPTIONS_HINT" = "View more option controls";
 "REPEAT_MODE_HINT" = "Change repeat mode of current item";
 "VIDEO_ASPECT_RATIO_HINT" = "Change the aspect ratio of the current video";
+
+/* MediaMoreOptionsActionSheet */
+"EQUALIZER_CELL_TITLE" = "Equalizer";
+"MORE_OPTIONS_HEADER_TITLE" = "Video Options";

+ 4 - 0
Sources/ActionSheet/ActionSheet.swift

@@ -208,6 +208,10 @@ class ActionSheet: UIViewController {
         }
         collectionView.layoutIfNeeded()
     }
+    
+    func addChildToStackView(_ child: UIView) {
+        mainStackView.addSubview(child)
+    }
 }
 
 // MARK: Private setup methods

+ 118 - 25
Sources/ActionSheet/ActionSheetCell.swift

@@ -9,8 +9,8 @@
  * Refer to the COPYING file of the official project for license.
  *****************************************************************************/
 
-enum ActionSheetCellAccessoryType: Equatable {
-    case none
+enum ActionSheetCellAccessoryType {
+    case toggleSwitch
     case checkmark
     case disclosureChevron
 }
@@ -33,9 +33,46 @@ class ActionSheetCellImageView: UIImageView {
     }
 }
 
+/// Model that determines the layout presentation of the ActionSheetCell.
+@objc (VLCActionSheetCellModel)
+@objcMembers class ActionSheetCellModel: NSObject {
+    var title: String
+    var iconImage: UIImage?
+    var viewToPresent: UIView?
+    var accessoryType: ActionSheetCellAccessoryType
+    var cellIdentifier: MediaPlayerActionSheetCellIdentifier?
+    
+    init(
+        title: String,
+        imageIdentifier: String,
+        accessoryType: ActionSheetCellAccessoryType = .checkmark,
+        viewToPresent: UIView? = nil,
+        cellIdentifier: MediaPlayerActionSheetCellIdentifier? = nil) {
+            self.title = title
+            iconImage = UIImage(named: imageIdentifier)?.withRenderingMode(.alwaysTemplate)
+            self.accessoryType = accessoryType
+            self.viewToPresent = viewToPresent
+            self.cellIdentifier = cellIdentifier
+    }
+}
+
+@objc (VLCActionSheetCellDelegate)
+protocol ActionSheetCellDelegate {
+    func actionSheetCellShouldUpdateColors() -> Bool
+    func actionSheetCellDidToggleSwitch(for cell: ActionSheetCell, state: Bool)
+}
+
 @objc(VLCActionSheetCell)
 class ActionSheetCell: UICollectionViewCell {
 
+    /// UIViewController to present on cell selection
+    weak var viewToPresent: UIView?
+    /// Rightmost accessory view that the cell should use. Default `checkmark`.
+    /// If `viewControllerToPresent` is set, defaults to `disclosureChevron`, otherwise `checkmark` is main default.
+    private(set) var accessoryView = UIView ()
+    weak var delegate: ActionSheetCellDelegate?
+    var identifier: MediaPlayerActionSheetCellIdentifier?
+
     @objc static var identifier: String {
         return String(describing: self)
     }
@@ -44,13 +81,16 @@ class ActionSheetCell: UICollectionViewCell {
         didSet {
             updateColors()
             // only checkmarks should be hidden if they arent selected
-            accessoryTypeImageView.isHidden = !isSelected && accessoryType == .checkmark
+            if accessoryType == .checkmark {
+                accessoryView.isHidden = !isSelected
+            }
         }
     }
 
     let icon: ActionSheetCellImageView = {
         let icon = ActionSheetCellImageView()
         icon.translatesAutoresizingMaskIntoConstraints = false
+        icon.setContentHuggingPriority(.required, for: .horizontal)
         icon.contentMode = .scaleAspectFit
         return icon
     }()
@@ -60,31 +100,42 @@ class ActionSheetCell: UICollectionViewCell {
         name.textColor = PresentationTheme.current.colors.cellTextColor
         name.font = UIFont.systemFont(ofSize: 15)
         name.translatesAutoresizingMaskIntoConstraints = false
-        name.setContentHuggingPriority(.defaultLow, for: .horizontal)
         return name
     }()
 
-    private var accessoryTypeImageView: UIImageView = {
+    lazy private var accessoryTypeImageView: UIImageView = {
         let imageView = UIImageView()
         imageView.contentMode = .scaleAspectFit
         imageView.backgroundColor = .none
-        imageView.tintColor = PresentationTheme.current.colors.cellDetailTextColor
+        imageView.setContentHuggingPriority(.required, for: .horizontal)
         imageView.translatesAutoresizingMaskIntoConstraints = false
         return imageView
     }()
+    
+    lazy private var toggleSwitch: UISwitch = {
+        let toggleSwitch = UISwitch()
+        toggleSwitch.onTintColor = .orange
+        toggleSwitch.translatesAutoresizingMaskIntoConstraints = false
+        toggleSwitch.addTarget(self, action: #selector(switchToggled(_:)), for: .valueChanged)
+        return toggleSwitch
+    }()
 
-    var accessoryType: ActionSheetCellAccessoryType = .checkmark {
+    private(set) var accessoryType: ActionSheetCellAccessoryType = .checkmark {
         didSet {
             switch accessoryType {
             case .checkmark:
                 accessoryTypeImageView.image = UIImage(named: "checkmark")?.withRenderingMode(.alwaysTemplate)
-                accessoryTypeImageView.isHidden = !isSelected
+                add(view: accessoryTypeImageView, to: accessoryView)
             case .disclosureChevron:
                 accessoryTypeImageView.image = UIImage(named: "disclosureChevron")?.withRenderingMode(.alwaysTemplate)
-                accessoryTypeImageView.isHidden = false
-            case .none:
-                accessoryTypeImageView.image = nil
-                accessoryTypeImageView.isHidden = true
+                add(view: accessoryTypeImageView, to: accessoryView)
+            case .toggleSwitch:
+                add(view: toggleSwitch, to: accessoryView)
+            }
+            if accessoryType == .checkmark {
+                accessoryView.isHidden = !isSelected
+            } else {
+                accessoryView.isHidden = false
             }
         }
     }
@@ -107,43 +158,85 @@ class ActionSheetCell: UICollectionViewCell {
         super.init(coder: aDecoder)
         setupViews()
     }
+    
+    convenience init(withCellModel model: ActionSheetCellModel) {
+        self.init()
+        configure(withModel: model)
+        setupViews()
+    }
 
     private func updateColors() {
+        let shouldUpdateColors = delegate?.actionSheetCellShouldUpdateColors() ?? true
         let colors = PresentationTheme.current.colors
-        name.textColor = isSelected ? colors.orangeUI : colors.cellTextColor
-        tintColor = isSelected ? colors.orangeUI : colors.cellDetailTextColor
-        if accessoryType == .checkmark {
-            let defaultColor = PresentationTheme.current.colors.cellDetailTextColor
-            accessoryTypeImageView.tintColor = isSelected ? .orange : defaultColor
+        if shouldUpdateColors {
+            name.textColor = isSelected ? colors.orangeUI : colors.cellTextColor
+            tintColor = isSelected ? colors.orangeUI : colors.cellDetailTextColor
         }
+        if accessoryType != .toggleSwitch {
+            accessoryView.tintColor = isSelected && accessoryType == .checkmark ? colors.orangeUI : colors.cellDetailTextColor
+        }
+    }
+
+    @objc private func switchToggled(_ sender: UISwitch) {
+        delegate?.actionSheetCellDidToggleSwitch(for: self, state: sender.isOn)
     }
 
     override func prepareForReuse() {
         super.prepareForReuse()
+        toggleSwitch.removeFromSuperview()
+        accessoryType = .checkmark
         updateColors()
     }
 
+    private func add(view: UIView, to parentView: UIView) {
+        view.translatesAutoresizingMaskIntoConstraints = false
+        parentView.subviews.forEach { $0.removeFromSuperview() }
+        parentView.addSubview(view)
+        NSLayoutConstraint.activate([
+            view.topAnchor.constraint(equalTo: parentView.topAnchor),
+            view.bottomAnchor.constraint(equalTo: parentView.bottomAnchor),
+            view.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
+            view.trailingAnchor.constraint(equalTo: parentView.trailingAnchor)
+        ])
+    }
+    
+    func configure(withModel model: ActionSheetCellModel) {
+        if model.accessoryType == .disclosureChevron {
+            assert(model.viewToPresent != nil, "ActionSheetCell: Cell with disclosure chevron must have accompanying presentable UIView")
+        }
+        name.text = model.title
+        icon.image = model.iconImage
+        viewToPresent = model.viewToPresent
+        identifier = model.cellIdentifier
+        // disclosure chevron is set as the default accessoryView if a viewController is present
+        accessoryType = model.viewToPresent != nil ? .disclosureChevron : model.accessoryType
+    }
+
+    func setToggleSwitch(state: Bool) {
+        if accessoryType == .toggleSwitch {
+            toggleSwitch.isOn = state
+        }
+    }
+
     private func setupViews() {
         backgroundColor = PresentationTheme.current.colors.background
 
-        // property observers only trigger after the first time the values are set.
-        // allow the didSet to set the checkmark image
-        accessoryType = .checkmark
-        
         stackView.addArrangedSubview(icon)
         stackView.addArrangedSubview(name)
-        stackView.addArrangedSubview(accessoryTypeImageView)
+        stackView.addArrangedSubview(accessoryView)
+
         addSubview(stackView)
 
+        // property observers only trigger after the first time the values are set.
+        // allow the didSet to set the checkmark image
+        accessoryType = .checkmark
+
         var guide: LayoutAnchorContainer = self
 
         if #available(iOS 11.0, *) {
             guide = safeAreaLayoutGuide
         }
         NSLayoutConstraint.activate([
-            icon.heightAnchor.constraint(equalToConstant: 25),
-            icon.widthAnchor.constraint(equalTo: icon.heightAnchor),
-
             stackView.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: 20),
             stackView.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: -20),
             stackView.heightAnchor.constraint(equalTo: heightAnchor),

+ 273 - 0
Sources/MediaMoreOptionsActionSheet.swift

@@ -0,0 +1,273 @@
+/*****************************************************************************
+ * MediaMoreOptionsActionSheet.swift
+ *
+ * Copyright © 2019 VLC authors and VideoLAN
+ *
+ * Authors: Robert Gordon <robwaynegordon@gmail.com>
+ *
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+enum MediaPlayerActionSheetCellIdentifier: String, CustomStringConvertible, CaseIterable {
+    case filter
+    case playback
+    case equalizer
+    case sleepTimer
+    case interfaceLock
+
+    var description: String {
+        switch self {
+        case .filter:
+            return NSLocalizedString("VIDEO_FILTER", comment: "")
+        case .playback:
+            return NSLocalizedString("PLAYBACK_SPEED", comment: "")
+        case .equalizer:
+            return NSLocalizedString("EQUALIZER_CELL_TITLE", comment: "")
+        case .sleepTimer:
+            return NSLocalizedString("BUTTON_SLEEP_TIMER", comment: "")
+        case .interfaceLock:
+            return NSLocalizedString("INTERFACE_LOCK_BUTTON", comment: "")
+        }
+    }
+}
+
+@objc (VLCMediaMoreOptionsActionSheetDelegate)
+protocol MediaMoreOptionsActionSheetDelegate {
+    func mediaMoreOptionsDidToggleInterfaceLock(state: Bool)
+}
+
+@objc (VLCMediaMoreOptionsActionSheet)
+class MediaMoreOptionsActionSheet: ActionSheet {
+    
+    // MARK: Private Instance Properties
+    private weak var currentChildView: UIView?
+    @objc weak var moreOptionsDelegate: MediaMoreOptionsActionSheetDelegate?
+
+    @objc var interfaceDisabled: Bool = false {
+        didSet {
+            collectionView.visibleCells.forEach {
+                if let cell = $0 as? ActionSheetCell, let id = cell.identifier {
+                    if id == .interfaceLock {
+                        cell.setToggleSwitch(state: interfaceDisabled)
+                    } else {
+                        cell.alpha = interfaceDisabled ? 0.5 : 1
+                    }
+                }
+            }
+            collectionView.allowsSelection = !interfaceDisabled
+        }
+    }
+
+    private var offScreenFrame: CGRect {
+        let y = collectionView.frame.origin.y + headerView.cellHeight
+        let w = collectionView.frame.size.width
+        let h = collectionView.frame.size.height
+        return CGRect(x: w, y: y, width: w, height: h)
+    }
+    
+    private var leftToRightGesture: UIPanGestureRecognizer {
+        let leftToRight = UIPanGestureRecognizer(target: self, action: #selector(draggedRight(panGesture:)))
+        return leftToRight
+    }
+    
+    // To be removed when Designs are done for the Filters, Equalizer etc views are added to Figma
+    lazy private var mockView: UIView = {
+        let v = UIView()
+        v.backgroundColor = .green
+        v.frame = offScreenFrame
+        return v
+    }()
+    
+    lazy private var cellModels: [ActionSheetCellModel] = {
+        var models: [ActionSheetCellModel] = []
+        MediaPlayerActionSheetCellIdentifier.allCases.forEach {
+            var cellModel = ActionSheetCellModel(
+                title: String(describing: $0),
+                imageIdentifier: $0.rawValue,
+                viewToPresent: mockView,
+                cellIdentifier: $0
+            )
+            if $0 == .interfaceLock {
+                cellModel.accessoryType = .toggleSwitch
+                cellModel.viewToPresent = nil
+            }
+            models.append(cellModel)
+        }
+        return models
+    }()
+    
+    // MARK: Private Methods
+    private func add(childView child: UIView) {
+        UIView.animate(withDuration: 0.3, animations: {
+            child.frame = self.collectionView.frame
+            self.addChildToStackView(child)
+        }) {
+            (completed) in
+            child.addGestureRecognizer(self.leftToRightGesture)
+            self.currentChildView = child
+        }
+    }
+    
+    private func remove(childView child: UIView) {
+        UIView.animate(withDuration: 0.3, animations: {
+            child.frame = self.offScreenFrame
+        }) { (completed) in
+            child.removeFromSuperview()
+            child.removeGestureRecognizer(self.leftToRightGesture)
+        }
+    }
+    
+    @objc func removeCurrentChild() {
+        if let current = currentChildView {
+            remove(childView: current)
+        }
+    }
+
+    func setTheme() {
+        let darkColors = PresentationTheme.darkTheme.colors
+        collectionView.backgroundColor = darkColors.background
+        headerView.backgroundColor = darkColors.background
+        headerView.title.textColor = darkColors.cellTextColor
+        for cell in collectionView.visibleCells {
+            if let cell = cell as? ActionSheetCell {
+                cell.backgroundColor = darkColors.background
+                cell.name.textColor = darkColors.cellTextColor
+                cell.icon.tintColor = .orange
+                // toggleSwitch's tintColor should not be changed
+                if cell.accessoryType == .disclosureChevron {
+                    cell.accessoryView.tintColor = darkColors.cellDetailTextColor
+                } else if cell.accessoryType == .checkmark {
+                    cell.accessoryView.tintColor = .orange
+                }
+            }
+        }
+        collectionView.layoutIfNeeded()
+    }
+
+    /// Animates the removal of the `currentChildViewController` when it is dragged from its left edge to the right
+    @objc private func draggedRight(panGesture: UIPanGestureRecognizer) {
+        if let current = currentChildView {
+
+            let translation = panGesture.translation(in: view)
+            let x = translation.x + current.center.x
+            let halfWidth = current.frame.size.width / 2
+            panGesture.setTranslation(.zero, in: view)
+
+            if panGesture.state == .began || panGesture.state == .changed {
+                // only enable left-to-right drags
+                if current.frame.minX + translation.x >= 0 {
+                    current.center = CGPoint(x: x, y: current.center.y)
+                }
+            } else if panGesture.state == .ended {
+                if current.frame.minX > halfWidth {
+                    removeCurrentChild()
+                } else {
+                    UIView.animate(withDuration: 0.3) {
+                        current.frame = self.collectionView.frame
+                    }
+                }
+            }
+        }
+    }
+    
+    // MARK: Overridden superclass methods
+
+    // Removed the automatic dismissal of the view when a cell is selected
+    override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        if let delegate = delegate {
+            if let item = delegate.itemAtIndexPath(indexPath) {
+                delegate.actionSheet?(collectionView: collectionView, didSelectItem: item, At: indexPath)
+                action?(item)
+            }
+            if let cell = collectionView.cellForItem(at: indexPath) as? ActionSheetCell, cell.accessoryType == .checkmark {
+                removeActionSheet()
+            }
+        }
+    }
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        // Remove the themeDidChangeNotification set in the superclass
+        // MovieViewController Video Options should be dark at all times
+        NotificationCenter.default.removeObserver(self, name: .VLCThemeDidChangeNotification, object: nil)
+        setTheme()
+    }
+    
+    // MARK: Initializers
+    override init() {
+        super.init()
+        delegate = self
+        dataSource = self
+        modalPresentationStyle = .custom
+        setAction { (item) in
+            if let item = item as? UIView {
+               self.add(childView: item)
+            } else {
+                preconditionFailure("MediaMoreOptionsActionSheet: Cell item could not be casted as UIView")
+            }
+        }
+        setTheme()
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+extension MediaMoreOptionsActionSheet: ActionSheetDataSource {
+    func numberOfRows() -> Int {
+        return cellModels.count
+    }
+    
+    func actionSheet(collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+
+        guard indexPath.row < cellModels.count else {
+            assertionFailure("MediaMoreOptionsActionSheet: Out of range.")
+            return ActionSheetCell()
+        }
+
+        var sheetCell: ActionSheetCell
+
+        if let cell = collectionView.dequeueReusableCell(
+            withReuseIdentifier: ActionSheetCell.identifier,
+            for: indexPath) as? ActionSheetCell {
+            sheetCell = cell
+            sheetCell.configure(withModel: cellModels[indexPath.row])
+        } else {
+            assertionFailure("MediaMoreOptionsActionSheet: Could not dequeue reusable cell")
+            sheetCell = ActionSheetCell(withCellModel: cellModels[indexPath.row])
+        }
+        sheetCell.accessoryView.tintColor = PresentationTheme.darkTheme.colors.cellDetailTextColor
+        sheetCell.delegate = self
+        return sheetCell
+    }
+}
+
+extension MediaMoreOptionsActionSheet: ActionSheetDelegate {
+    func itemAtIndexPath(_ indexPath: IndexPath) -> Any? {
+        if indexPath.row < cellModels.count {
+            return cellModels[indexPath.row].viewToPresent
+        }
+        return nil
+    }
+    
+    func headerViewTitle() -> String? {
+        return NSLocalizedString("MORE_OPTIONS_HEADER_TITLE", comment: "")
+    }
+}
+
+extension MediaMoreOptionsActionSheet: ActionSheetCellDelegate {
+    func actionSheetCellShouldUpdateColors() -> Bool {
+        return false
+    }
+
+    func actionSheetCellDidToggleSwitch(for cell: ActionSheetCell, state: Bool) {
+        assert(moreOptionsDelegate != nil, "MediaMoreOptionsActionSheet: Delegate not set.")
+        if let identifier = cell.identifier {
+            if identifier == .interfaceLock {
+                moreOptionsDelegate?.mediaMoreOptionsDidToggleInterfaceLock(state: state)
+            }
+        }
+    }
+}

+ 21 - 22
Sources/VLCMovieViewController.m

@@ -54,7 +54,7 @@ typedef NS_ENUM(NSInteger, VLCPanType) {
   VLCPanTypeProjection
 };
 
-@interface VLCMovieViewController () <UIGestureRecognizerDelegate, VLCMultiSelectionViewDelegate, VLCEqualizerViewUIDelegate, VLCPlaybackControllerDelegate, VLCDeviceMotionDelegate, VLCRendererDiscovererManagerDelegate, PlaybackSpeedViewDelegate, VLCVideoOptionsControlBarDelegate>
+@interface VLCMovieViewController () <UIGestureRecognizerDelegate, VLCMultiSelectionViewDelegate, VLCEqualizerViewUIDelegate, VLCPlaybackControllerDelegate, VLCDeviceMotionDelegate, VLCRendererDiscovererManagerDelegate, PlaybackSpeedViewDelegate, VLCVideoOptionsControlBarDelegate, VLCMediaMoreOptionsActionSheetDelegate>
 {
     BOOL _controlsHidden;
     BOOL _videoFiltersHidden;
@@ -98,6 +98,7 @@ typedef NS_ENUM(NSInteger, VLCPanType) {
     VLCEqualizerView *_equalizerView;
     VLCMultiSelectionMenuView *_multiSelectionView;
     VLCVideoOptionsControlBar *_videoOptionsControlBar;
+    VLCMediaMoreOptionsActionSheet *_moreOptionsActionSheet;
 
     VLCPlaybackController *_vpc;
 
@@ -171,6 +172,8 @@ typedef NS_ENUM(NSInteger, VLCPanType) {
         [self setupMultiSelectionView];
     #else
         [self setupVideoOptionsControlBar];
+        _moreOptionsActionSheet = [[VLCMediaMoreOptionsActionSheet alloc] init];
+        _moreOptionsActionSheet.moreOptionsDelegate = self;
     #endif
 
     _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
@@ -256,7 +259,6 @@ typedef NS_ENUM(NSInteger, VLCPanType) {
 {
     _videoOptionsControlBar = [[VLCVideoOptionsControlBar alloc] init];
     _videoOptionsControlBar.delegate = self;
-    _videoOptionsControlBar.hidden = YES;
     _videoOptionsControlBar.repeatMode = _vpc.repeatMode;
     [self.view addSubview:_videoOptionsControlBar];
 }
@@ -704,7 +706,6 @@ typedef NS_ENUM(NSInteger, VLCPanType) {
         return [arr arrayByAddingObjectsFromArray:
                     @[_videoOptionsControlBar.toggleFullScreenButton,
                       _videoOptionsControlBar.selectSubtitleButton,
-                      _videoOptionsControlBar.moreOptionsButton,
                       _videoOptionsControlBar.repeatButton]];
     #endif
 }
@@ -813,7 +814,7 @@ typedef NS_ENUM(NSInteger, VLCPanType) {
         #if !NEW_UI
             self->_multiSelectionView.hidden = YES;
         #else
-            self->_videoOptionsControlBar.hidden = YES;
+            self->_videoOptionsControlBar.hidden = NO;
         #endif
         
 
@@ -1346,25 +1347,10 @@ currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
                      completion:nil];
 }
 
-- (void) toggleVideoOptionsBar
-{
-    CGFloat alpha =  _videoOptionsControlBar.hidden ? 1.0f : 0.0f;
-    BOOL hidden = !_videoOptionsControlBar.hidden;
-
-    [UIView animateWithDuration:.3
-                     animations:^{
-                         self->_videoOptionsControlBar.alpha = alpha;
-                         self->_videoOptionsControlBar.hidden = hidden;
-                     }
-                     completion:nil];
-}
-
 - (void)moreActions:(UIButton *)sender
 {
     #if !NEW_UI
         [self toggleMultiSelectionView:sender];
-    #else
-        [self toggleVideoOptionsBar];
     #endif
     [self _resetIdleTimer];
 }
@@ -1401,6 +1387,7 @@ currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
         _multiSelectionView.displayLock = _interfaceIsLocked;
     #else
         _videoOptionsControlBar.interfaceDisabled = _interfaceIsLocked;
+        _moreOptionsActionSheet.interfaceDisabled = _interfaceIsLocked;
     #endif
 }
 
@@ -1483,8 +1470,7 @@ currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
                          self->_videoOptionsControlBar.hidden = YES;
                     #endif
                      }
-                     completion:^(BOOL finished){
-                     }];
+                     completion:nil];
     [self _resetIdleTimer];
 }
 
@@ -1873,7 +1859,7 @@ currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
 #pragma mark - VLCVideoOptionsControlBarDelegate
 
 - (void)didSelectMoreOptions:(VLCVideoOptionsControlBar * _Nonnull)optionsBar {
-    [self moreActions:optionsBar.moreOptionsButton];
+    [self toggleMoreOptionsActionSheet];
 }
 
 - (void)didSelectSubtitle:(VLCVideoOptionsControlBar * _Nonnull)optionsBar {
@@ -1892,4 +1878,17 @@ currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
     [self toggleRepeatMode];
 }
 
+- (void)toggleMoreOptionsActionSheet
+{
+    [self presentViewController:_moreOptionsActionSheet animated:false completion:^{
+        self->_moreOptionsActionSheet.interfaceDisabled = self->_interfaceIsLocked;
+    }];
+}
+
+#pragma mark - VLCMediaMoreOptionsActionSheetDelegate
+
+- (void) mediaMoreOptionsDidToggleInterfaceLockWithState:(BOOL)state
+{
+    [self toggleUILock];
+}
 @end

+ 4 - 0
VLC.xcodeproj/project.pbxproj

@@ -384,6 +384,7 @@
 		DDF908E01CF4E04A00108B70 /* VLCNetworkLoginDataSourceSavedLogins.m in Sources */ = {isa = PBXBuildFile; fileRef = DDF908DF1CF4E04A00108B70 /* VLCNetworkLoginDataSourceSavedLogins.m */; };
 		DDF908E41CFCD97400108B70 /* VLCNetworkLoginDataSourceProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = DDF908E31CFCD97400108B70 /* VLCNetworkLoginDataSourceProtocol.m */; };
 		E03DECA622BEE27E00F44A05 /* MediaNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E03DECA522BEE27E00F44A05 /* MediaNavigationBar.swift */; };
+		E03F36B122ED8DBB00057525 /* MediaMoreOptionsActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E03F36B022ED8DBA00057525 /* MediaMoreOptionsActionSheet.swift */; };
 		E0539E8C22E5E2EC009317CB /* VideoOptionsControlBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0539E8B22E5E2EC009317CB /* VideoOptionsControlBar.swift */; };
 		E0BA21B922D7C8C700937CFD /* MediaPlaybackControlToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0BA21B822D7C8C700937CFD /* MediaPlaybackControlToolbar.swift */; };
 		E0C04F951A25B4410080331A /* VLCDocumentPickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C04F941A25B4410080331A /* VLCDocumentPickerController.m */; };
@@ -1022,6 +1023,7 @@
 		DDF908E21CFCD97400108B70 /* VLCNetworkLoginDataSourceProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCNetworkLoginDataSourceProtocol.h; path = Sources/LocalNetworkConnectivity/VLCNetworkLoginDataSourceProtocol.h; sourceTree = SOURCE_ROOT; };
 		DDF908E31CFCD97400108B70 /* VLCNetworkLoginDataSourceProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCNetworkLoginDataSourceProtocol.m; path = Sources/LocalNetworkConnectivity/VLCNetworkLoginDataSourceProtocol.m; sourceTree = SOURCE_ROOT; };
 		E03DECA522BEE27E00F44A05 /* MediaNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MediaNavigationBar.swift; path = Sources/MediaNavigationBar.swift; sourceTree = "<group>"; };
+		E03F36B022ED8DBA00057525 /* MediaMoreOptionsActionSheet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MediaMoreOptionsActionSheet.swift; path = Sources/MediaMoreOptionsActionSheet.swift; sourceTree = "<group>"; };
 		E0539E8B22E5E2EC009317CB /* VideoOptionsControlBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VideoOptionsControlBar.swift; path = Sources/VideoOptionsControlBar.swift; sourceTree = "<group>"; };
 		E0BA21B822D7C8C700937CFD /* MediaPlaybackControlToolbar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MediaPlaybackControlToolbar.swift; path = Sources/MediaPlaybackControlToolbar.swift; sourceTree = "<group>"; };
 		E0C04F931A25B4410080331A /* VLCDocumentPickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCDocumentPickerController.h; path = Sources/VLCDocumentPickerController.h; sourceTree = SOURCE_ROOT; };
@@ -1406,6 +1408,7 @@
 			isa = PBXGroup;
 			children = (
 				E0BA21B822D7C8C700937CFD /* MediaPlaybackControlToolbar.swift */,
+				E03F36B022ED8DBA00057525 /* MediaMoreOptionsActionSheet.swift */,
 				8D144D6122298E5800984C46 /* MiniPlayer */,
 				416443852048419E00CAC646 /* DeviceMotion.swift */,
 				DD8F842F1B00EB3B0009138A /* VLCPlaybackController+MediaLibrary.h */,
@@ -2960,6 +2963,7 @@
 				DD3EFF5D1BDEBCE500B68579 /* VLCLocalServerDiscoveryController.m in Sources */,
 				E0539E8C22E5E2EC009317CB /* VideoOptionsControlBar.swift in Sources */,
 				D6E034ED1CC284FC0037F516 /* VLCStreamingHistoryCell.m in Sources */,
+				E03F36B122ED8DBB00057525 /* MediaMoreOptionsActionSheet.swift in Sources */,
 				DD3EFEED1BDEBA3800B68579 /* VLCNetworkServerBrowserViewController.m in Sources */,
 				8DF9669D2113317E00D0FCD6 /* EditToolbar.swift in Sources */,
 				416443862048419E00CAC646 /* DeviceMotion.swift in Sources */,

+ 23 - 0
vlc-ios/Images.xcassets/VideoOptions/iconSubtitle.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "subtitlesIcon-new.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "subtitlesIcon-new@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "subtitlesIcon-new@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}