Commit ea54eeb7 authored by Carola Nitz's avatar Carola Nitz

Initial Drag and Drop support

parent e53f95d4
......@@ -17,4 +17,8 @@ NSLocalizedString(@"STORE_DESCRIPTION", nil);
NSLocalizedString(@"STORE_DESCRIPTION_TV", nil);
NSLocalizedString(@"ABOUT_APP", nil);
NSLocalizedString(@"SECTION_HEADER_NETWORK", nil);
NSLocalizedString(@"THIS_FILE", nil);
NSLocalizedString(@"NOT_SUPPORTED_FILETYPE", nil);
NSLocalizedString(@"FILE_EXISTS", nil);
}
......@@ -324,6 +324,12 @@
"DELETE_MESSAGE" = "Confirm the deletion of the selected files";
"DELETE_TITLE" = "Delete Selected Files";
//Drag and Drop
"THIS_FILE" = "This file";
"NOT_SUPPORTED_FILETYPE" = "%@ is not a supported filetype by VLC";
"FILE_EXISTS" = "%@ already exists";
/* New strings */
"PROTOCOL_NOT_SELECTED" = "PROTOCOL_NOT_SELECTED";
"Settings" = "Settings";
/*****************************************************************************
* VLCDragAndDropManager.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 UIKit
import MobileCoreServices
@available(iOS 11.0, *)
struct DropError: Error {
enum ErrorKind {
case moveFileToDocuments
case loadFileRepresentationFailed
}
let kind: ErrorKind
}
@available(iOS 11.0, *)
@objc protocol VLCDragAndDropManagerDelegate : NSObjectProtocol {
func dragAndDropManagerRequestsFile(manager:VLCDragAndDropManager, atIndexPath indexPath:IndexPath) -> AnyObject?
func dragAndDropManagerInsertItem(manager:VLCDragAndDropManager, item:NSManagedObject, atIndexPath indexPath:IndexPath)
func dragAndDropManagerDeleteItem(manager:VLCDragAndDropManager, atIndexPath indexPath:IndexPath)
func dragAndDropManagerRemoveFileFromFolder(manager:VLCDragAndDropManager, file:NSManagedObject)
func dragAndDropManagerCurrentSelection(manager:VLCDragAndDropManager) -> AnyObject?
}
@available(iOS 11.0, *)
class VLCDragAndDropManager: NSObject, UICollectionViewDragDelegate, UITableViewDragDelegate, UICollectionViewDropDelegate, UITableViewDropDelegate, UIDropInteractionDelegate
{
@objc weak var delegate:VLCDragAndDropManagerDelegate?
lazy var utiTypeIdentifiers:[String] = {
self.supportedTypeIdentifiers()
}()
/// Returns the supported type identifiers that VLC can process.
/// It fetches the identifiers in LSItemContentTypes from all the CFBundleDocumentTypes in the info.plist.
/// Video, Audio and Subtitle formats
///
/// - Returns: Array of UTITypeIdentifiers
private func supportedTypeIdentifiers() -> [String] {
var typeIdentifiers:[String] = []
let documentTypes = Bundle.main.object(forInfoDictionaryKey: "CFBundleDocumentTypes")
if let documents = documentTypes as? Array<AnyObject> {
for item in documents {
if let object = item as? [NSString:Any] {
for (key, value) in object where key == "LSItemContentTypes"{
typeIdentifiers.append(contentsOf: value as! Array)
}
}
}
}
return typeIdentifiers
}
//MARK: - TableView
func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool {
return canHandleDropSession(session: session)
}
func tableView(_ tableView: UITableView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
return dragItems(forIndexPath:indexPath)
}
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
return dragItems(forIndexPath:indexPath)
}
func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
let operation = dropOperation(hasActiveDrag: tableView.hasActiveDrag, firtSessionItem: session.items.first, withDestinationIndexPath: destinationIndexPath)
return UITableViewDropProposal(operation: operation, intent: .insertIntoDestinationIndexPath)
}
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
let section = tableView.numberOfSections - 1
let row = tableView.numberOfRows(inSection: section)
let destinationPath = coordinator.destinationIndexPath ?? IndexPath(row: row, section: section)
for item in coordinator.items {
let itemProvider = item.dragItem.itemProvider
//we're not gonna handle moving of folders
if let sourceItem = item.dragItem.localObject, fileIsCollection(file: sourceItem as AnyObject) {
continue
}
if fileIsFolder(atIndexPath:destinationPath) { //handle dropping onto a folder
addDragItem(tableView:tableView, dragItem:item, toFolderAt:destinationPath)
continue
}
if item.sourceIndexPath != nil { //element within VLC
moveItem(tableView:tableView, item:item, toIndexPath:destinationPath)
continue
}
//Element dragging from another App
let placeholder = UITableViewDropPlaceholder(insertionIndexPath: destinationPath, reuseIdentifier: "PlaylistCell", rowHeight: VLCPlaylistTableViewCell.heightOfCell())
let placeholderContext = coordinator.drop(item.dragItem, to: placeholder)
createFileWith(itemProvider:itemProvider) {
[weak self] file, error in
guard let strongSelf = self else { return }
if let file = file {
placeholderContext.commitInsertion() {
insertionIndexPath in
strongSelf.delegate?.dragAndDropManagerInsertItem(manager: strongSelf, item: file, atIndexPath: insertionIndexPath)
}
}
if let error = error as? DropError {
strongSelf.handleError(error: error, itemProvider: item.dragItem.itemProvider)
placeholderContext.deletePlaceholder()
}
}
}
}
private func inFolder() -> Bool {
return delegate?.dragAndDropManagerCurrentSelection(manager: self) as? MLLabel != nil
}
private func moveItem(tableView:UITableView, item:UITableViewDropItem, toIndexPath destinationPath:IndexPath) {
if let mlFile = item.dragItem.localObject as? MLFile, mlFile.labels.count > 0 && !inFolder() {
tableView.performBatchUpdates({
tableView.insertRows(at: [destinationPath], with: .automatic)
delegate?.dragAndDropManagerInsertItem(manager: self, item: mlFile, atIndexPath: destinationPath)
delegate?.dragAndDropManagerRemoveFileFromFolder(manager:self, file:mlFile)
}, completion:nil)
}
}
private func addDragItem(tableView:UITableView, dragItem item:UITableViewDropItem, toFolderAt index:IndexPath) {
if let sourcepath = item.sourceIndexPath { //local file that just needs to be moved
tableView.performBatchUpdates({
tableView.deleteRows(at: [sourcepath], with: .automatic)
if let file = delegate?.dragAndDropManagerRequestsFile(manager:self, atIndexPath: sourcepath) as? MLFile {
addFile(file:file, toFolderAt:index)
delegate?.dragAndDropManagerDeleteItem(manager: self, atIndexPath:sourcepath)
}
}, completion:nil)
return
}
// file from other app
createFileWith(itemProvider:item.dragItem.itemProvider) {
[weak self] file, error in
if let strongSelf = self, let file = file {
strongSelf.addFile(file:file, toFolderAt:index)
}
}
}
//MARK: - Collectionview
func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool {
return canHandleDropSession(session: session)
}
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
return dragItems(forIndexPath: indexPath)
}
func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
return dragItems(forIndexPath: indexPath)
}
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
let operation = dropOperation(hasActiveDrag: collectionView.hasActiveDrag, firtSessionItem: session.items.first, withDestinationIndexPath: destinationIndexPath)
return UICollectionViewDropProposal(operation: operation, intent: .insertIntoDestinationIndexPath)
}
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
let section = collectionView.numberOfSections - 1
let row = collectionView.numberOfItems(inSection: section)
let destinationPath = coordinator.destinationIndexPath ?? IndexPath(row: row, section: section)
for item in coordinator.items {
if let sourceItem = item.dragItem.localObject, fileIsCollection(file: sourceItem as AnyObject) { //We're not handling moving of Collection
continue
}
if fileIsFolder(atIndexPath:destinationPath) { //handle dropping onto a folder
addDragItem(collectionView:collectionView, dragItem:item, toFolderAt:destinationPath)
continue
}
if item.sourceIndexPath != nil { //element within VLC
moveItem(collectionView:collectionView, item:item, toIndexPath:destinationPath)
continue
}
//Element from another App
let placeholder = UICollectionViewDropPlaceholder(insertionIndexPath: destinationPath, reuseIdentifier: "PlaylistCell")
let placeholderContext = coordinator.drop(item.dragItem, to: placeholder)
createFileWith(itemProvider:item.dragItem.itemProvider) {
[weak self] file, error in
guard let strongSelf = self else { return }
if let file = file {
placeholderContext.commitInsertion() {
insertionIndexPath in
strongSelf.delegate?.dragAndDropManagerInsertItem(manager: strongSelf, item: file, atIndexPath: insertionIndexPath)
}
}
if let error = error as? DropError {
strongSelf.handleError(error: error, itemProvider: item.dragItem.itemProvider)
placeholderContext.deletePlaceholder()
}
}
}
}
private func moveItem(collectionView:UICollectionView, item:UICollectionViewDropItem, toIndexPath destinationPath:IndexPath) {
if let mlFile = item.dragItem.localObject as? MLFile, mlFile.labels.count > 0 && !inFolder() {
collectionView.performBatchUpdates({
collectionView.insertItems(at: [destinationPath])
delegate?.dragAndDropManagerInsertItem(manager: self, item: mlFile, atIndexPath: destinationPath)
delegate?.dragAndDropManagerRemoveFileFromFolder(manager:self, file:mlFile)
}, completion:nil)
}
}
private func addDragItem(collectionView:UICollectionView, dragItem item:UICollectionViewDropItem, toFolderAt index:IndexPath) {
if let sourcepath = item.sourceIndexPath {
//local file that just needs to be moved
collectionView.performBatchUpdates({
collectionView.deleteItems(at:[sourcepath])
if let file = delegate?.dragAndDropManagerRequestsFile(manager:self, atIndexPath: sourcepath) as? MLFile {
addFile(file:file, toFolderAt:index)
delegate?.dragAndDropManagerDeleteItem(manager: self, atIndexPath:sourcepath)
}
}, completion:nil)
} else {
// file from other app
createFileWith(itemProvider:item.dragItem.itemProvider) {
[weak self] file, error in
if let strongSelf = self, let file = file {
strongSelf.addFile(file:file, toFolderAt:index)
}
}
}
}
//MARK: - DropInteractionDelegate for EmptyView
func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
return canHandleDropSession(session: session)
}
func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
return UIDropProposal(operation: .copy)
}
func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
for item in session.items {
createFileWith(itemProvider:item.itemProvider) {
[weak self] _, error in
if let error = error as? DropError {
self?.handleError(error: error, itemProvider: item.itemProvider)
}
//no need to handle the file case since the libraryVC updates itself after getting a file
}
}
}
//MARK: - Shared Methods
//Checks if the session has items conforming to typeidentifiers
private func canHandleDropSession(session:UIDropSession) -> Bool {
if (session.localDragSession != nil) {
return true
}
return session.hasItemsConforming(toTypeIdentifiers: utiTypeIdentifiers)
}
/// Returns a drop operation type
///
/// - Parameters:
/// - hasActiveDrag: State if the drag started within the app
/// - item: UIDragItem from session
/// - Returns: UIDropOperation
private func dropOperation(hasActiveDrag: Bool, firtSessionItem item: AnyObject?, withDestinationIndexPath destinationIndexPath:IndexPath?) -> UIDropOperation {
let inAlbum = delegate?.dragAndDropManagerCurrentSelection(manager: self) as? MLAlbum != nil
let inShow = delegate?.dragAndDropManagerCurrentSelection(manager: self) as? MLShow != nil
//you can move files into a folder or copy from anothr app into a folder
if fileIsFolder(atIndexPath:destinationIndexPath) {
return hasActiveDrag ? .move : .copy
}
//you can't reorder
if inFolder() {
return hasActiveDrag ? .forbidden : .copy
}
//you can't reorder in or drag into an Album or Show
if inAlbum || inShow {
return .cancel
}
//we're dragging a file out of a folder
if let dragItem = item, let mlFile = dragItem.localObject as? MLFile, mlFile.labels.count > 0 {
return .copy
}
//no reorder from another app into the top layer
return hasActiveDrag ? .forbidden : .copy
}
/// show an Alert when dropping failed
///
/// - Parameters:
/// - error: the type of error that happend
/// - itemProvider: the itemProvider to retrieve the suggestedName
private func handleError(error: DropError, itemProvider: NSItemProvider) {
let message: String
let filename = itemProvider.suggestedName ?? NSLocalizedString("THIS_FILE", comment:"")
switch (error.kind) {
case .loadFileRepresentationFailed:
message = String(format: NSLocalizedString("NOT_SUPPORTED_FILETYPE", comment: ""), filename)
case .moveFileToDocuments:
message = String(format: NSLocalizedString("FILE_EXISTS", comment: ""), filename)
}
let alert = UIAlertController(title: NSLocalizedString("ERROR", comment:""), message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment:""), style: .default, handler: nil))
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)
}
private func fileIsFolder(atIndexPath indexPath:IndexPath?) -> Bool {
if let indexPath = indexPath {
let file = delegate?.dragAndDropManagerRequestsFile(manager: self, atIndexPath: indexPath)
return file as? MLLabel != nil
}
return false
}
private func fileIsCollection(file:AnyObject?) -> Bool {
let isFolder = file as? MLLabel != nil
let isAlbum = file as? MLAlbum != nil
let isShow = file as? MLShow != nil
return isFolder || isAlbum || isShow
}
private func fileIsCollection(atIndexPath indexPath:IndexPath?) -> Bool {
if let indexPath = indexPath {
let file = delegate?.dragAndDropManagerRequestsFile(manager: self, atIndexPath: indexPath)
return fileIsCollection(file:file)
}
return false
}
//creating dragItems for the file at indexpath
private func dragItems(forIndexPath indexPath:IndexPath) -> [UIDragItem] {
if let file = delegate?.dragAndDropManagerRequestsFile(manager: self, atIndexPath: indexPath) {
if fileIsCollection(atIndexPath: indexPath) {
return dragItemsforCollection(file: file)
}
return dragItem(fromFile:file)
}
assert(false, "we can't generate a dragfile if the delegate can't return a file ")
return []
}
/// Iterates over the items of a collection to create dragitems.
/// Since we're not storing collections as folders we have to provide single files
///
/// - Parameter file: Can be of type MLAlbum, MLLabel or MLShow
/// - Returns: An array of UIDragItems
private func dragItemsforCollection(file: AnyObject) -> [UIDragItem] {
var dragItems = [UIDragItem]()
var set = Set<AnyHashable>()
if let folder = file as? MLLabel {
set = folder.files
} else if let album = file as? MLAlbum {
for track in album.tracks {
if let mlfile = (track as? MLAlbumTrack)?.files.first {
_ = set.insert(mlfile)
}
}
} else if let show = file as? MLShow {
for episode in show.episodes {
if let mlfile = (episode as? MLShowEpisode)?.files.first {
_ = set.insert(mlfile)
}
}
} else {
assert(false, "can't get dragitems from a file that is not a collection")
}
for convertibleFile in set {
if let mlfile = convertibleFile as? MLFile, let item = dragItem(fromFile:mlfile).first {
dragItems.append(item)
}
}
return dragItems
}
//Provides an item for other applications
private func dragItem(fromFile file:AnyObject) -> [UIDragItem] {
guard let file = mlFile(from: file), let path = file.url else {
assert(false, "can't create a dragitem if there is no file or the file has no url")
return []
}
let data = try? Data(contentsOf: path, options: .mappedIfSafe)
let itemProvider = NSItemProvider()
itemProvider.suggestedName = path.lastPathComponent
//maybe use UTTypeForFileURL
if let identifiers = try? path.resourceValues(forKeys: [.typeIdentifierKey]), let identifier = identifiers.typeIdentifier {
//here we can show progress
itemProvider.registerDataRepresentation(forTypeIdentifier: identifier, visibility: .all) { completion -> Progress? in
completion(data, nil)
return nil
}
let dragitem = UIDragItem(itemProvider: itemProvider)
dragitem.localObject = file
return [dragitem]
}
assert(false, "we can't provide a typeidentifier")
return []
}
private func mlFile(from file:AnyObject) -> MLFile? {
if let episode = file as? MLShowEpisode, let convertedfile = episode.files.first as? MLFile{
return convertedfile
}
if let track = file as? MLAlbumTrack, let convertedfile = track.files.first as? MLFile{
return convertedfile
}
if let convertedfile = file as? MLFile {
return convertedfile
}
return nil
}
private func addFile(file:MLFile, toFolderAt folderIndex:IndexPath) {
let label = delegate?.dragAndDropManagerRequestsFile(manager: self, atIndexPath: folderIndex) as! MLLabel
DispatchQueue.main.async {
_ = label.files.insert(file)
file.labels = [label]
file.folderTrackNumber = NSNumber(integerLiteral: label.files.count - 1)
}
}
/// try to create a file from the dropped item
///
/// - Parameters:
/// - itemProvider: itemprovider which is used to load the files from
/// - completion: callback with the successfully created file or error if it failed
private func createFileWith(itemProvider:NSItemProvider, completion: @escaping ((MLFile?, Error?) -> Void))
{
itemProvider.loadFileRepresentation(forTypeIdentifier: kUTTypeData as String) {
[weak self] (url, error) in
guard let strongSelf = self else { return }
guard let url = url else {
DispatchQueue.main.async {
completion(nil, DropError(kind: .loadFileRepresentationFailed))
}
return
}
//returns nil for local session but this should also not be called for a local session
guard let destinationURL = strongSelf.moveFileToDocuments(fromURL: url) else {
DispatchQueue.main.async {
completion(nil, DropError(kind: .moveFileToDocuments))
}
return
}
DispatchQueue.global(qos: .background).async {
let sharedlib = MLMediaLibrary.sharedMediaLibrary() as? MLMediaLibrary
sharedlib?.addFilePaths([destinationURL.path])
if let file = MLFile.file(for: destinationURL).first as? MLFile {
DispatchQueue.main.async {
//we dragged into a folder
if let selection = strongSelf.delegate?.dragAndDropManagerCurrentSelection(manager: strongSelf) as? MLLabel {
file.labels = [selection]
}
completion(file, nil)
}
}
}
}
}
private func moveFileToDocuments(fromURL filepath:URL?) -> URL? {
let searchPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let newDirectoryPath = searchPaths.first
guard let directoryPath = newDirectoryPath, let url = filepath else {
return nil
}
let destinationURL = URL(fileURLWithPath: "\(directoryPath)" + "/" + "\(url.lastPathComponent)")
do {
try FileManager.default.moveItem(at: url, to: destinationURL)
} catch let error {
print(error.localizedDescription)
return nil
}
return destinationURL
}
}
......@@ -30,12 +30,14 @@
#import "VLCPlaylistTableViewCell.h"
#import "VLCPlaybackController+MediaLibrary.h"
#import "VLC_iOS-Swift.h"
#import <CoreSpotlight/CoreSpotlight.h>
/* prefs keys */
static NSString *kDisplayedFirstSteps = @"Did we display the first steps tutorial?";
static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
@implementation EmptyLibraryView
- (IBAction)learnMore:(id)sender
......@@ -71,6 +73,8 @@ static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
UIBarButtonItem *_shareBarButtonItem;
UIBarButtonItem *_removeFromFolderBarButtonItem;
UIBarButtonItem *_deleteSelectedBarButtonItem;
NSObject *dragAndDropManager;
}
@property (nonatomic, strong) UIBarButtonItem *displayModeBarButtonItem;
......@@ -83,9 +87,6 @@ static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
@implementation VLCLibraryViewController
- (void)dealloc
{
}
+ (void)initialize
{
......@@ -96,16 +97,21 @@ static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
- (void)loadView
{
[self setupContentView];
[self setViewFromDeviceOrientation];
[self updateViewsForCurrentDisplayMode];
_libraryMode = VLCLibraryModeAllFiles;
_searchDataSource = [VLCLibrarySearchDisplayDataSource new];
_mediaDataSource = [VLCMediaDataSource new];
_searchDataSource = [VLCLibrarySearchDisplayDataSource new];
self.emptyLibraryView = [[[NSBundle mainBundle] loadNibNamed:@"VLCEmptyLibraryView" owner:self options:nil] lastObject];
_emptyLibraryView.emptyLibraryLongDescriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
_emptyLibraryView.emptyLibraryLongDescriptionLabel.numberOfLines = 0;
if (@available(iOS 11.0, *)) {
dragAndDropManager = [VLCDragAndDropManager new];
((VLCDragAndDropManager *)dragAndDropManager).delegate = _mediaDataSource;
UIDropInteraction *dropInteraction = [[UIDropInteraction alloc] initWithDelegate:(VLCDragAndDropManager *)dragAndDropManager];
[_emptyLibraryView addInteraction:dropInteraction];
}
[self setupContentView];
[self setViewFromDeviceOrientation];
[self updateViewsForCurrentDisplayMode];
_libraryMode = VLCLibraryModeAllFiles;
}
- (void)setupContentView
......@@ -114,7 +120,6 @@ static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
UIView *contentView = [[UIView alloc] initWithFrame:viewDimensions];
contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
contentView.backgroundColor = [UIColor VLCDarkBackgroundColor];
if (self.usingTableViewToShowData) {
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:viewDimensions style:UITableViewStylePlain];
......@@ -128,6 +133,10 @@ static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
_tableView.separatorColor = [UIColor VLCDarkBackgroundColor];
_tableView.delegate = self;
_tableView.dataSource = self;
if (@available(iOS 11.0, *)) {
_tableView.dragDelegate = ((VLCDragAndDropManager *)dragAndDropManager);
_tableView.dropDelegate = ((VLCDragAndDropManager *)dragAndDropManager);
}
_tableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
_tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
_tableView.tableHeaderView = _searchController.searchBar;
......@@ -142,6 +151,10 @@ static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
_folderLayout = [[VLCFolderCollectionViewFlowLayout alloc] init];
_collectionView = [[UICollectionView alloc] initWithFrame:viewDimensions collectionViewLayout:_folderLayout];
_collectionView.alwaysBounceVertical = YES;
if (@available(iOS 11.0, *)) {
_collectionView.dragDelegate = ((VLCDragAndDropManager *)dragAndDropManager);
_collectionView.dropDelegate = ((VLCDragAndDropManager *)dragAndDropManager);
}
_collectionView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
_collectionView.delegate = self;
_collectionView.dataSource = self;
......@@ -440,7 +453,7 @@ static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
}
self.navigationItem.leftBarButtonItem = _menuButton;
[_mediaDataSource removeAllObjects];
[_mediaDataSource updateContentsForSelection:nil];
_removeFromFolderBarButtonItem.enabled = NO;
switch (_libraryMode) {
......@@ -480,6 +493,7 @@ static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
_isSelected = NO;
} else {
[self.collectionView reloadData];
[self updateActionBarButtonItemStateWithSelectedIndexPaths:[self.collectionView indexPathsForSelectedItems]];
if (_libraryMode == VLCLibraryModeAllFiles) {
if (self.collectionView.collectionViewLayout != _folderLayout) {
......@@ -528,6 +542,17 @@ static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
if (@available(iOS 11.0, *)) {
return true;
}
return _inFolder;
}
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath
{
if (@available(iOS 11.0, *)) {
return true;
}
return _inFolder;
}
......@@ -712,6 +737,12 @@ static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
[_mediaDataSource moveObjectFromIndex:fromIndexPath.item toIndex:toIndexPath.item];
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSpringLoadItemAtIndexPath:(NSIndexPath *)indexPath withContext:(id<UISpringLoadedInteractionContext>)context NS_AVAILABLE_IOS(11.0)
{
id mediaObject = [_mediaDataSource objectAtIndex:indexPath.item];
return [mediaObject isKindOfClass:[MLLabel class]];
}
- (void)collectionView:(UICollectionView *)collectionView requestToMoveItemAtIndexPath:(NSIndexPath *)itemPath intoFolderAtIndexPath:(NSIndexPath *)folderPath
{
id folderPathItem = [_mediaDataSource objectAtIndex:folderPath.item];
......@@ -798,7 +829,7 @@ static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
NSArray *folder = [MLLabel allLabels];
//if we already have folders display them
if ([folder count] > 0) {
[_mediaDataSource removeAllObjects];
[_mediaDataSource updateContentsForSelection:nil];
[_mediaDataSource addAllFolders];
self.title = NSLocalizedString(@"SELECT_FOLDER", nil);
_previousLibraryMode = _libraryMode;
......
......@@ -10,6 +10,8 @@
@interface VLCMediaDataSource : NSObject
- (NSManagedObject *)currentSelection;
- (void)updateContentsForSelection:(NSManagedObject *)selection;
- (NSUInteger)numberOfFiles;
- (NSManagedObject *)objectAtIndex:(NSUInteger)index;
......@@ -18,7 +20,6 @@
- (void)insertObject:(NSManagedObject *)object atIndex:(NSUInteger)index;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)moveObjectFromIndex:(NSUInteger)fromIdx toIndex:(NSUInteger)toIdx;
- (void)removeAllObjects;
//this always creates a copy that might not be good
- (NSArray *)allObjects;
......
......@@ -13,6 +13,7 @@
@interface VLCMediaDataSource ()
{
NSMutableArray *_foundMedia;
NSManagedObject *_currentSelection;
}
@end
......@@ -20,7 +21,7 @@
- (void)updateContentsForSelection:(NSManagedObject *)selection
{
NSArray *array;
NSArray *array = [NSMutableArray new];