VLCLibraryViewController.m 59.7 KB
Newer Older
1
/*****************************************************************************
2
 * VLCLibraryViewController.m
3 4
 * VLC for iOS
 *****************************************************************************
5
 * Copyright (c) 2013-2017 VideoLAN. All rights reserved.
6 7 8 9 10
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
 *          Gleb Pinigin <gpinigin # gmail.com>
 *          Tamas Timar <ttimar.vlc # gmail.com>
11
 *          Carola Nitz <nitz.carola # gmail.com>
12
 *          Tobias Conradi <videolan # tobias-conradi.de>
13 14 15
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
Felix Paul Kühne's avatar
Felix Paul Kühne committed
16

17
#import "VLCLibraryViewController.h"
18 19 20

#import "LXReorderableCollectionViewFlowLayout.h"
#import "VLCActivityViewControllerVendor.h"
21
#import "VLCAppDelegate.h"
22
#import "VLCBugreporter.h"
23
#import "VLCFirstStepsViewController.h"
24
#import "VLCFolderCollectionViewFlowLayout.h"
25
#import "VLCMediaDataSource.h"
26
#import "VLCLibrarySearchDisplayDataSource.h"
27 28 29 30
#import "VLCMovieViewController.h"
#import "VLCPlaylistCollectionViewCell.h"
#import "VLCPlaylistTableViewCell.h"

31
#import "VLCPlaybackController+MediaLibrary.h"
Carola Nitz's avatar
Carola Nitz committed
32
#import "VLC_iOS-Swift.h"
33

34
#import <CoreSpotlight/CoreSpotlight.h>
35

36 37
/* prefs keys */
static NSString *kDisplayedFirstSteps = @"Did we display the first steps tutorial?";
38
static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
Carola Nitz's avatar
Carola Nitz committed
39

40
@implementation EmptyLibraryView
41 42 43 44

- (IBAction)learnMore:(id)sender
{
    UIViewController *firstStepsVC = [[VLCFirstStepsViewController alloc] initWithNibName:nil bundle:nil];
45
    UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:firstStepsVC];
46 47 48 49
    navCon.modalPresentationStyle = UIModalPresentationFormSheet;
    [self.window.rootViewController presentViewController:navCon animated:YES completion:nil];
}

50 51
@end

52
@interface VLCLibraryViewController () <VLCFolderCollectionViewDelegateFlowLayout, LXReorderableCollectionViewDataSource, LXReorderableCollectionViewDelegateFlowLayout, UITableViewDataSource, UITableViewDelegate, MLMediaLibrary, VLCMediaListDelegate, UISearchResultsUpdating, UISearchControllerDelegate> {
53
    VLCLibraryMode _libraryMode;
54
    VLCLibraryMode _previousLibraryMode;
55
    UIBarButtonItem *_menuButton;
56 57 58 59
    NSMutableArray *_indexPaths;
    id _folderObject;
    VLCFolderCollectionViewFlowLayout *_folderLayout;
    LXReorderableCollectionViewFlowLayout *_reorderLayout;
60
    BOOL _inFolder;
Soomin Lee's avatar
Soomin Lee committed
61
    BOOL _isSelected;
62
    BOOL _deleteFromTableView;
63
    UILongPressGestureRecognizer *_longPressGestureRecognizer;
64

65
    VLCLibrarySearchDisplayDataSource *_searchDataSource;
66 67
    VLCMediaDataSource *_mediaDataSource;

68
    UISearchController *_searchController;
69

Soomin Lee's avatar
Soomin Lee committed
70
    UIBarButtonItem *_selectAllBarButtonItem;
71
    UIBarButtonItem *_createFolderBarButtonItem;
72
    UIBarButtonItem *_shareBarButtonItem;
73
    UIBarButtonItem *_removeFromFolderBarButtonItem;
74
    UIBarButtonItem *_deleteSelectedBarButtonItem;
Soomin Lee's avatar
Soomin Lee committed
75
    UIBarButtonItem *_rendererBarButton;
Carola Nitz's avatar
Carola Nitz committed
76 77
    
    NSObject *dragAndDropManager;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
78
}
79

80
@property (nonatomic, strong) UIBarButtonItem *displayModeBarButtonItem;
81
@property (nonatomic, strong) UITableView *tableView;
82
@property (nonatomic, strong) UICollectionView *collectionView;
83 84
@property (nonatomic, strong) EmptyLibraryView *emptyLibraryView;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
85 86
@end

87
@implementation VLCLibraryViewController
Felix Paul Kühne's avatar
Felix Paul Kühne committed
88

89

90 91 92 93
+ (void)initialize
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults registerDefaults:@{kDisplayedFirstSteps : [NSNumber numberWithBool:NO]}];
94
    [defaults registerDefaults:@{kUsingTableViewToShowData : [NSNumber numberWithBool:UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone]}];
95 96
}

97 98
- (void)loadView
{
99
    _mediaDataSource = [VLCMediaDataSource new];
Carola Nitz's avatar
Carola Nitz committed
100
    _searchDataSource = [VLCLibrarySearchDisplayDataSource new];
101 102 103
    self.emptyLibraryView = [[[NSBundle mainBundle] loadNibNamed:@"VLCEmptyLibraryView" owner:self options:nil] lastObject];
    _emptyLibraryView.emptyLibraryLongDescriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
    _emptyLibraryView.emptyLibraryLongDescriptionLabel.numberOfLines = 0;
Carola Nitz's avatar
Carola Nitz committed
104 105 106 107 108 109 110 111 112 113
    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;
114 115
}

116
- (void)setupContentView
117
{
Tobias's avatar
Tobias committed
118
    CGRect viewDimensions = [UIApplication sharedApplication].keyWindow.bounds;
119 120 121
    UIView *contentView = [[UIView alloc] initWithFrame:viewDimensions];
    contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
    contentView.backgroundColor = [UIColor VLCDarkBackgroundColor];
122 123 124 125 126 127 128 129 130 131 132 133 134

    if (!_tableView) {
        _tableView = [[UITableView alloc] initWithFrame:viewDimensions style:UITableViewStylePlain];
        _tableView.backgroundColor = [UIColor VLCDarkBackgroundColor];
        CGRect frame = _tableView.bounds;
        frame.origin.y = -frame.size.height;
        UIView *topView = [[UIView alloc] initWithFrame:frame];
        topView.backgroundColor = [UIColor VLCDarkBackgroundColor];
        [_tableView addSubview:topView];
        _tableView.rowHeight = [VLCPlaylistTableViewCell heightOfCell];
        _tableView.separatorColor = [UIColor VLCDarkBackgroundColor];
        _tableView.delegate = self;
        _tableView.dataSource = self;
135 136
        _tableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
        _tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
137 138 139
        if (@available(iOS 11.0, *)) {
            _tableView.dragDelegate = ((VLCDragAndDropManager *)dragAndDropManager);
            _tableView.dropDelegate = ((VLCDragAndDropManager *)dragAndDropManager);
140 141
        } else {
            _tableView.tableHeaderView = _searchController.searchBar;
142
        }
143 144 145 146 147 148 149 150
        UINib *nib = [UINib nibWithNibName:@"VLCPlaylistTableViewCell" bundle:nil];
        [_tableView registerNib:nib forCellReuseIdentifier:kPlaylistCellIdentifier];
    }
    _tableView.frame = contentView.bounds;
    [_tableView reloadData];

    if (!_collectionView) {
        _folderLayout = [[VLCFolderCollectionViewFlowLayout alloc] init];
151
        _reorderLayout = [[LXReorderableCollectionViewFlowLayout alloc] init];
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
        _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;
        _collectionView.backgroundColor = [UIColor VLCDarkBackgroundColor];
        _collectionView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
        _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_collectionViewHandleLongPressGesture:)];
        [_collectionView addGestureRecognizer:_longPressGestureRecognizer];
        [_collectionView registerNib:[UINib nibWithNibName:@"VLCPlaylistCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"PlaylistCell"];
    }
    _collectionView.frame = contentView.bounds;
    [_collectionView reloadData];
    if (self.usingTableViewToShowData) {
170
        [contentView addSubview:_tableView];
171 172
        if (@available(iOS 11.0, *))
            [self setSearchBar:YES resetContent:NO];
173
    } else {
174
        [contentView addSubview:_collectionView];
175
        [_searchController setActive:NO];
176 177
        if (@available(iOS 11.0, *))
            [self setSearchBar:NO resetContent:NO];
178
    }
179
    self.view = contentView;
180 181 182 183
}

#pragma mark -

184 185 186 187 188 189 190
- (void)viewWillLayoutSubviews {
    UIScrollView *dataView = self.usingTableViewToShowData ? _tableView : _collectionView;

    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
    [dataView setContentInset:UIEdgeInsetsZero];
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
191 192 193
- (void)viewDidLoad
{
    [super viewDidLoad];
194
    self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", nil);
195 196
    _menuButton = [UIBarButtonItem themedRevealMenuButtonWithTarget:self andSelector:@selector(leftButtonAction:)];
    self.navigationItem.leftBarButtonItem = _menuButton;
197

198
    self.editButtonItem.title = NSLocalizedString(@"BUTTON_EDIT", nil);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
199
    self.editButtonItem.tintColor = [UIColor whiteColor];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
200

Soomin Lee's avatar
Soomin Lee committed
201 202 203 204 205 206
    _selectAllBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"BUTTON_ALL", nil) style:UIBarButtonItemStylePlain target:self action:@selector(handleSelection)];
    _selectAllBarButtonItem.tintColor = [UIColor whiteColor];
    UIFont *font = [UIFont boldSystemFontOfSize:17];
    NSDictionary *attributes = @{NSFontAttributeName: font};
    [_selectAllBarButtonItem setTitleTextAttributes:attributes forState:UIControlStateNormal];

207 208
    _emptyLibraryView.emptyLibraryLabel.text = NSLocalizedString(@"EMPTY_LIBRARY", nil);
    _emptyLibraryView.emptyLibraryLongDescriptionLabel.text = NSLocalizedString(@"EMPTY_LIBRARY_LONG", nil);
209
    [_emptyLibraryView.emptyLibraryLongDescriptionLabel sizeToFit];
210
    [_emptyLibraryView.learnMoreButton setTitle:NSLocalizedString(@"BUTTON_LEARN_MORE", nil) forState:UIControlStateNormal];
211
    _createFolderBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemOrganize target:self action:@selector(createFolder)];
212 213

    // Better visual alignment with the action button
214 215
    _createFolderBarButtonItem.imageInsets = UIEdgeInsetsMake(3, 0, -3, 0);
    _createFolderBarButtonItem.landscapeImagePhoneInsets = UIEdgeInsetsMake(2, 0, -2, 0);
216 217

    _removeFromFolderBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemReply target:self action:@selector(removeFromFolder)];
218 219
    _removeFromFolderBarButtonItem.imageInsets = UIEdgeInsetsMake(2, 0, -2, 0);
    _removeFromFolderBarButtonItem.landscapeImagePhoneInsets = UIEdgeInsetsMake(1, 0, -1, 0);
220
    _removeFromFolderBarButtonItem.enabled = NO;
221

222 223
    _shareBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(share:)];
    _shareBarButtonItem.enabled = NO;
224

225 226 227
    _deleteSelectedBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteSelection:)];
    _deleteFromTableView = NO;

228 229 230 231 232 233 234
    UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    fixedSpace.width = 20;

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        fixedSpace.width *= 2;
    }

235
    [self setToolbarItems:@[_shareBarButtonItem,
236 237 238 239 240 241
                            fixedSpace,
                            _createFolderBarButtonItem,
                            [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil],
                            [UIBarButtonItem themedDarkToolbarButtonWithTitle:NSLocalizedString(@"BUTTON_RENAME", nil) target:self andSelector:@selector(renameSelection)],
                            [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil],
                            _removeFromFolderBarButtonItem,
242
                            fixedSpace,
243
                            _deleteSelectedBarButtonItem]];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
244
    self.navigationController.toolbar.barStyle = UIBarStyleBlack;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
245 246
    self.navigationController.toolbar.tintColor = [UIColor whiteColor];
    [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
247

248
    self.edgesForExtendedLayout = UIRectEdgeNone;
249

250
    [self setupSearchController];
251 252 253
    UIButton *rendererButton = [VLCRendererDiscovererManager.sharedInstance setupRendererButton];
    [rendererButton sizeToFit];
    _rendererBarButton = [[UIBarButtonItem alloc] initWithCustomView:rendererButton];
254 255 256 257 258 259 260 261 262
}

- (void)setupSearchController
{
    _searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
    _searchController.searchResultsUpdater = self;
    _searchController.dimsBackgroundDuringPresentation = NO;
    _searchController.delegate = self;
    [self setSearchBar:YES resetContent:YES];
263 264 265 266
    if (!self.usingTableViewToShowData) {
        if (@available(iOS 11.0, *))
            [self setSearchBar:NO resetContent:NO];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
267 268
}

269 270 271
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
272
    [self.collectionView.collectionViewLayout invalidateLayout];
273 274
    [self setViewFromDeviceOrientation];
    [self updateViewsForCurrentDisplayMode];
Soomin Lee's avatar
Soomin Lee committed
275 276 277 278 279 280 281

    VLCRendererDiscovererManager *manager = VLCRendererDiscovererManager.sharedInstance;
    if (manager.discoverers || manager.discoverers.count) {
        // No discoverers has yet been started
        [manager start];
    }
    manager.presentingViewController = self;
282 283
}

284 285
- (void)viewDidAppear:(BOOL)animated
{
286 287
    [super viewDidAppear:animated];

288 289 290 291 292 293
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    if (![[defaults objectForKey:kDisplayedFirstSteps] boolValue]) {
        [self.emptyLibraryView performSelector:@selector(learnMore:) withObject:nil afterDelay:1.];
        [defaults setObject:[NSNumber numberWithBool:YES] forKey:kDisplayedFirstSteps];
    }

294
    if ([_mediaDataSource numberOfFiles] < 1)
295
        [self updateViewContents];
296 297 298 299 300 301 302 303 304
    [[MLMediaLibrary sharedMediaLibrary] performSelector:@selector(libraryDidAppear) withObject:nil afterDelay:1.];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    [[MLMediaLibrary sharedMediaLibrary] libraryDidDisappear];
}

305 306 307 308 309 310 311 312 313 314 315
- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
    if (motion == UIEventSubtypeMotionShake)
        [[VLCBugreporter sharedInstance] handleBugreportRequest];
}

316 317
- (void)openMediaObject:(NSManagedObject *)mediaObject
{
318
    if ([mediaObject isKindOfClass:[MLAlbum class]] || [mediaObject isKindOfClass:[MLShow class]]) {
319
        [_mediaDataSource updateContentsForSelection:mediaObject];
320
        BOOL isAlbum = [mediaObject isKindOfClass:[MLAlbum class]];
321

322
        self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedBackButtonWithTarget:self andSelector:@selector(backToAllItems:)];
323
        if (_libraryMode == VLCLibraryModeAllFiles)
324
            self.navigationItem.leftBarButtonItem.title = NSLocalizedString(@"BUTTON_BACK", nil);
325
        else
326
            [self.navigationItem.leftBarButtonItem setTitle: isAlbum ? NSLocalizedString(@"LIBRARY_MUSIC", nil) : NSLocalizedString(@"LIBRARY_SERIES", nil)];
327 328
        self.title = [(MLAlbum*)mediaObject name];
        [self reloadViews];
329
    } else if ([mediaObject isKindOfClass:[MLLabel class]]) {
330
        [_mediaDataSource updateContentsForSelection:mediaObject];
331
        _inFolder = YES;
332
        if (!self.usingTableViewToShowData) {
333 334 335 336 337 338 339
            if (@available(iOS 11.0, *)) {
            } else {
                if (![self.collectionView.collectionViewLayout isEqual:_reorderLayout]) {
                    for (UIGestureRecognizer *recognizer in _collectionView.gestureRecognizers) {
                        if (recognizer == _folderLayout.panGestureRecognizer || recognizer == _folderLayout.longPressGestureRecognizer || recognizer == _longPressGestureRecognizer)
                            [self.collectionView removeGestureRecognizer:recognizer];
                    }
340 341
                    //reloadData before setting a new layout avoids a crash deep in UIKits UICollectionViewData layoutattributs
                    [self.collectionView reloadData];
342
                    [self.collectionView setCollectionViewLayout:_reorderLayout animated:NO];
343
                }
344
            }
345
        }
346
        _libraryMode = VLCLibraryModeFolder;
347

348
        self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedBackButtonWithTarget:self andSelector:@selector(backToAllItems:)];
349
        self.navigationItem.leftBarButtonItem.title = NSLocalizedString(@"BUTTON_BACK", nil);
350
        self.title = [(MLLabel*)mediaObject name];
351

352 353
        _removeFromFolderBarButtonItem.enabled = YES;
        _createFolderBarButtonItem.enabled = NO;
354 355 356

        [self reloadViews];
        return;
357 358 359
    } else {
        VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
        [vpc playMediaLibraryObject:mediaObject];
360 361 362 363 364 365 366

        [self createSpotlightItem:mediaObject];
    }
}

- (void)createSpotlightItem:(nonnull NSManagedObject *)mediaObject
{
367
    if (![VLCKeychainCoordinator passcodeLockEnabled]) {
368 369 370 371 372
        NSURL *uriRepresentation = mediaObject.objectID.URIRepresentation;
        if (uriRepresentation == nil) {
            return;
        }

373
        self.userActivity = [[NSUserActivity alloc] initWithActivityType:kVLCUserActivityPlaying];
374 375

        MLFile *file = nil;
376
        if ([mediaObject isKindOfClass:[MLAlbumTrack class]]) {
377
            file = [(MLAlbumTrack *)mediaObject anyFileFromTrack];
378
        } else if ([mediaObject isKindOfClass:[MLShowEpisode class]]) {
379 380 381
            file = [(MLShowEpisode *)mediaObject anyFileFromEpisode];
        } else if ([mediaObject isKindOfClass:[MLFile class]]){
            file = (MLFile *)mediaObject;
382
        }
383

384 385
        self.userActivity.title = file.title;
        self.userActivity.contentAttributeSet = file.coreSpotlightAttributeSet;
386
        self.userActivity.userInfo = @{ @"playingmedia": uriRepresentation};
387 388 389
        self.userActivity.eligibleForSearch = YES;
        self.userActivity.eligibleForHandoff = YES;
        [self.userActivity becomeCurrent];
390
    }
391 392
}

393
- (void)removeMediaObject:(id)managedObject updateDatabase:(BOOL)updateDB
394
{
395
    [_mediaDataSource removeMediaObject:managedObject];
396

397 398 399 400
    if (updateDB) {
        [[MLMediaLibrary sharedMediaLibrary] updateMediaDatabase];
        [self updateViewContents];
    }
401 402
}

403 404
- (void)_displayEmptyLibraryViewIfNeeded
{
405 406 407
    if (self.emptyLibraryView.superview)
        [self.emptyLibraryView removeFromSuperview];

408
    if ([_mediaDataSource numberOfFiles] == 0) {
409 410 411 412
        _inFolder = (_libraryMode == VLCLibraryModeFolder || _libraryMode == VLCLibraryModeCreateFolder);
        self.emptyLibraryView.emptyLibraryLabel.text = _inFolder ? NSLocalizedString(@"FOLDER_EMPTY", nil) : NSLocalizedString(@"EMPTY_LIBRARY", nil);
        self.emptyLibraryView.emptyLibraryLongDescriptionLabel.text = _inFolder ? NSLocalizedString(@"FOLDER_EMPTY_LONG", nil) : NSLocalizedString(@"EMPTY_LIBRARY_LONG", nil);
        self.emptyLibraryView.learnMoreButton.hidden = _inFolder;
Gleb Pinigin's avatar
Gleb Pinigin committed
413
        self.emptyLibraryView.frame = self.view.bounds;
414
        [self.view addSubview:self.emptyLibraryView];
415
        self.navigationItem.rightBarButtonItems = nil;
416
    } else {
417
        self.navigationItem.rightBarButtonItems = @[self.editButtonItem, _rendererBarButton];
418
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
419 420
            self.displayModeBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"tableViewIcon"] style:UIBarButtonItemStylePlain target:self action:@selector(toggleDisplayedView:)];
            self.navigationItem.rightBarButtonItems = @[self.displayModeBarButtonItem, self.editButtonItem, _rendererBarButton];
421 422
        }
    }
423 424
    if (self.usingTableViewToShowData) {
        _tableView.separatorStyle = [_mediaDataSource numberOfFiles] > 0 ? UITableViewCellSeparatorStyleSingleLine:
425
                                                             UITableViewCellSeparatorStyleNone;
426
    } else {
427
        [self.collectionView.collectionViewLayout invalidateLayout];
428
    }
429
    [self updateViewsForCurrentDisplayMode];
430 431
}

432
- (void)setViewFromDeviceOrientation
433
{
434
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
435
        BOOL isPortrait = YES;
436

Carola Nitz's avatar
Carola Nitz committed
437 438
        if (self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact)
            isPortrait = NO;
439

440
        [self setUsingTableViewToShowData:isPortrait];
441
        [self _displayEmptyLibraryViewIfNeeded];
442 443 444 445 446
    }
}

- (void)setSearchBar:(BOOL)enable resetContent:(BOOL)resetContent
{
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
    if (@available(iOS 11.0, *)) {
        _searchController.dimsBackgroundDuringPresentation = NO;
        [_searchController.searchBar sizeToFit];

        // search bar text field background color
        UITextField *searchTextField = [_searchController.searchBar valueForKey:@"searchField"];
        UIView *backgroundView = searchTextField.subviews.firstObject;
        backgroundView.backgroundColor = UIColor.whiteColor;
        backgroundView.layer.cornerRadius = 10;
        backgroundView.clipsToBounds = YES;

        _searchController.hidesNavigationBarDuringPresentation = NO;
        _searchController.obscuresBackgroundDuringPresentation = NO;
        self.navigationItem.hidesSearchBarWhenScrolling = YES;
        self.navigationItem.searchController = enable ? _searchController : nil;
        self.definesPresentationContext = YES;
    } else {
        _tableView.tableHeaderView = enable ? _searchController.searchBar : nil;
        if (resetContent) {
            CGPoint contentOffset = _tableView.contentOffset;
            contentOffset.y += CGRectGetHeight(_tableView.tableHeaderView.frame);
            _tableView.contentOffset = contentOffset;
        }
470 471 472
    }
}

473 474
- (void)libraryUpgradeComplete
{
475
    self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", nil);
Gleb Pinigin's avatar
Gleb Pinigin committed
476
    self.navigationItem.leftBarButtonItem = _menuButton;
477
    self.emptyLibraryView.emptyLibraryLongDescriptionLabel.hidden = NO;
478
    self.emptyLibraryView.emptyLibraryLabel.text = NSLocalizedString(@"EMPTY_LIBRARY", nil);
479 480 481
    [self.emptyLibraryView.activityIndicator stopAnimating];
    [self.emptyLibraryView removeFromSuperview];

482 483 484 485
    [self updateViewContents];
}

- (void)updateViewContents
Felix Paul Kühne's avatar
Felix Paul Kühne committed
486
{
487
    self.navigationItem.leftBarButtonItem = _menuButton;
Carola Nitz's avatar
Carola Nitz committed
488
    [_mediaDataSource updateContentsForSelection:nil];
489
    _removeFromFolderBarButtonItem.enabled = NO;
490

491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
    switch (_libraryMode) {
        case VLCLibraryModeAllAlbums: {
            self.title = NSLocalizedString(@"LIBRARY_MUSIC", nil);
            _createFolderBarButtonItem.enabled = NO;
            [_mediaDataSource addAlbumsInAllAlbumMode:YES];
        } break;
        case VLCLibraryModeAllSeries: {
            self.title = NSLocalizedString(@"LIBRARY_SERIES", nil);
            _createFolderBarButtonItem.enabled = NO;
            [_mediaDataSource addAllShows];
        } break;
        //Todo: I'm not sure if updateViewContents should be called in VLCLibraryModeFolder
        //Here should maybe be an NSAssert to prevent this but for now due to refactoring these calls would've been made in that case
        case VLCLibraryModeAllFiles:
        case VLCLibraryModeFolder:
        case VLCLibraryModeCreateFolder: {
            self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", nil);
            _createFolderBarButtonItem.enabled = YES;

            [_mediaDataSource addAlbumsInAllAlbumMode:NO];
            [_mediaDataSource addAllShows];
            [_mediaDataSource addAllFolders];
            [_mediaDataSource addRemainingFiles];
514
        }
515
    }
516
    [self reloadViews];
517
}
518

519
- (void)reloadViews
520
{
521
    // Since this gets can get called at any point and wipe away the selections, we update the actionBarButtonItem here because this can happen if you tap "Save Video" in the UIActivityController and a media access alert view takes away focus (the corresponding 'became active' notification of UIApplication will call this). Or simply try bringing down the notification center to trigger this. Any existing UIActivityViewController session should be safe as it would have copies of the selected file references.
522
    if (self.usingTableViewToShowData) {
523
        [self.tableView reloadData];
524
        [self updateActionBarButtonItemStateWithSelectedIndexPaths:[self.tableView indexPathsForSelectedRows]];
525
        _isSelected = NO;
526
    } else {
Felix Paul Kühne's avatar
Felix Paul Kühne committed
527
        [self.collectionView reloadData];
Carola Nitz's avatar
Carola Nitz committed
528

529
        [self updateActionBarButtonItemStateWithSelectedIndexPaths:[self.collectionView indexPathsForSelectedItems]];
530 531 532
        if (_libraryMode == VLCLibraryModeAllFiles) {
            if (self.collectionView.collectionViewLayout != _folderLayout) {
                for (UIGestureRecognizer *recognizer in _collectionView.gestureRecognizers) {
533 534
                    if (recognizer == _reorderLayout.panGestureRecognizer ||
                        recognizer == _reorderLayout.longPressGestureRecognizer) {
535
                        [self.collectionView removeGestureRecognizer:recognizer];
536
                    }
537 538 539 540 541
                }
                [self.collectionView setCollectionViewLayout:_folderLayout animated:NO];
                [self.collectionView addGestureRecognizer:_longPressGestureRecognizer];
            }
        }
542
    }
543 544

    [self _displayEmptyLibraryViewIfNeeded];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
545 546
}

547 548
#pragma mark - Table View

Felix Paul Kühne's avatar
Felix Paul Kühne committed
549 550
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
551
    return [_mediaDataSource numberOfFiles];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
552 553 554 555 556
}

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
557
    static NSString *CellIdentifier = @"PlaylistCell";
Felix Paul Kühne's avatar
Felix Paul Kühne committed
558

559
    VLCPlaylistTableViewCell *cell = (VLCPlaylistTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
560

561
    UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRightOnTableViewCellGestureAction:)];
562 563 564
    [swipeRight setDirection:(UISwipeGestureRecognizerDirectionRight)];
    [cell addGestureRecognizer:swipeRight];

565
    cell.mediaObject = [_mediaDataSource objectAtIndex:indexPath.row];
566

Felix Paul Kühne's avatar
Felix Paul Kühne committed
567 568 569
    return cell;
}

570 571
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
572
    [_mediaDataSource moveObjectFromIndex:fromIndexPath.item toIndex:toIndexPath.item];
573 574 575 576
}

- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
Carola Nitz's avatar
Carola Nitz committed
577 578 579 580 581 582 583 584 585 586 587
    if (@available(iOS 11.0, *)) {
        return true;
    }
    return _inFolder;
}

- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath
{
    if (@available(iOS 11.0, *)) {
        return true;
    }
588
    return _inFolder;
589 590
}

591 592
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
593
    cell.backgroundColor = (indexPath.row % 2 == 0)? [UIColor blackColor]: [UIColor VLCDarkBackgroundColor];
594
    cell.multipleSelectionBackgroundView.backgroundColor = cell.backgroundColor;
595 596
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
597 598
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
599 600
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        NSInteger row = indexPath.row;
601
        _deleteFromTableView = YES;
602
        if (row < [_mediaDataSource numberOfFiles])
603
            [self deleteSelection:indexPath];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
604
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
605 606
}

607 608 609 610 611 612 613
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (tableView.isEditing) {
        [self updateActionBarButtonItemStateWithSelectedIndexPaths:[tableView indexPathsForSelectedRows]];
    }
}

614
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Felix Paul Kühne's avatar
Felix Paul Kühne committed
615
{
616 617 618
    VLCPlaylistTableViewCell *playlistTableViewCell = (VLCPlaylistTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
    if ([playlistTableViewCell isExpanded]) {
        [playlistTableViewCell collapsWithAnimation:YES];
619 620 621
        return;
    }

622 623 624
    for (VLCPlaylistTableViewCell *cell in [tableView visibleCells]) {
        if ([cell isExpanded])
            [cell collapsWithAnimation:NO];
625 626
    }

627 628
    if (tableView.isEditing) {
        if (_libraryMode == VLCLibraryModeCreateFolder) {
629
            _folderObject = [_mediaDataSource objectAtIndex:indexPath.row];
630 631 632
            _libraryMode = _previousLibraryMode;
            [self updateViewContents];
            [self createFolderWithName:nil];
633 634
        } else {
            [self updateActionBarButtonItemStateWithSelectedIndexPaths:[tableView indexPathsForSelectedRows]];
635
        }
636
        return;
637
    }
638

639
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
640

641
    [self openMediaObject:playlistTableViewCell.mediaObject];
642 643
}

644
#pragma mark - Gesture Action
645
- (void)swipeRightOnTableViewCellGestureAction:(UIGestureRecognizer *)recognizer
646
{
647 648
    [self setEditing:!self.tableView.isEditing animated:YES];
    if (!self.tableView.isEditing) {
649 650
        NSIndexPath *path = [self.tableView indexPathForRowAtPoint:[recognizer locationInView:self.tableView]];
        [self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:path.row inSection:path.section]
651 652
                                animated:YES
                          scrollPosition:UITableViewScrollPositionNone];
653
        [self updateActionBarButtonItemStateWithSelectedIndexPaths:[self.tableView indexPathsForSelectedRows]];
654 655 656
    }
}

657 658 659 660 661 662 663
- (void)swipeRightOnCollectionViewCellGestureAction:(UIGestureRecognizer *)recognizer
{
    NSIndexPath *path = [self.collectionView indexPathForItemAtPoint:[recognizer locationInView:self.collectionView]];
    VLCPlaylistCollectionViewCell *cell = (VLCPlaylistCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:path];
    [cell showMetadata:!cell.showsMetaData];
}

664 665 666
#pragma mark - Collection View
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
667
    return [_mediaDataSource numberOfFiles];
668 669 670 671 672
}

- (UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    VLCPlaylistCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"PlaylistCell" forIndexPath:indexPath];
673 674

    cell.mediaObject = [_mediaDataSource objectAtIndex:indexPath.row];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
675

676 677 678 679
    cell.collectionView = _collectionView;

    [cell setEditing:self.editing animated:NO];

680 681 682 683
    UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRightOnCollectionViewCellGestureAction:)];
    [swipeRight setDirection:(UISwipeGestureRecognizerDirectionRight)];
    [cell addGestureRecognizer:swipeRight];

684 685 686 687 688
    return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
689
    const CGFloat maxCellWidth = [UIScreen mainScreen].bounds.size.width / 3;
Tobias's avatar
Tobias committed
690
    const CGFloat aspectRatio = 9.0/16.0;
691 692 693 694 695 696

    CGRect windowFrame = [UIApplication sharedApplication].keyWindow.frame;
    CGFloat windowWidth = windowFrame.size.width;

    int numberOfCellsPerRow = ceil(windowWidth/maxCellWidth);
    CGFloat cellWidth = windowWidth/numberOfCellsPerRow;
697
    cellWidth -= 5;
698 699

    return CGSizeMake(cellWidth, cellWidth * aspectRatio);
700 701 702 703
}

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
704
    return UIEdgeInsetsMake(5, 5, 5, 5);
705 706
}

707 708
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
709
    return 2.5;
710 711 712 713
}

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
714
    return 0.;
715 716
}

717 718
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
719
    if (self.editing) {
720
        if (_libraryMode == VLCLibraryModeCreateFolder) {
721
            _folderObject = [_mediaDataSource objectAtIndex:indexPath.item];
722
            [self updateViewContents];
723
            [self createFolderWithName:nil];
724
             _libraryMode = _previousLibraryMode;
725 726
        } else {
            [self updateActionBarButtonItemStateWithSelectedIndexPaths:[collectionView indexPathsForSelectedItems]];
727
        }
728 729 730 731
        [(VLCPlaylistCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath] selectionUpdate];
        return;
    }

732
    [collectionView deselectItemAtIndexPath:indexPath animated:NO];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
733 734
    NSArray *visibleCells = [[collectionView visibleCells] copy];
    NSUInteger cellCount = visibleCells.count;
735

736 737 738 739 740 741
    for (NSUInteger x = 0; x < cellCount; x++) {
        VLCPlaylistCollectionViewCell *cell = visibleCells[x];
        if ([cell showsMetaData])
            [cell showMetadata:NO];
    }

742 743
    NSManagedObject *selectedObject = [_mediaDataSource objectAtIndex:indexPath.row];

Felix Paul Kühne's avatar
Felix Paul Kühne committed
744 745
    if (selectedObject != nil)
        [self openMediaObject:selectedObject];
746 747
}

748 749
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
750 751 752 753
    if (self.editing) {
        [self updateActionBarButtonItemStateWithSelectedIndexPaths:[collectionView indexPathsForSelectedItems]];
    }

754 755 756
    [(VLCPlaylistCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath] selectionUpdate];
}

757 758
- (void)collectionView:(UICollectionView *)collectionView removeItemFromFolderAtIndexPathIfNeeded:(NSIndexPath *)indexPath
{
759
    id mediaObject = [_mediaDataSource objectAtIndex:indexPath.item];
760

761
    [_mediaDataSource removeMediaObjectFromFolder:mediaObject];
762 763 764 765

    [self backToAllItems:nil];
}

766 767
- (void)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath willMoveToIndexPath:(NSIndexPath *)toIndexPath
{
768
    [_mediaDataSource moveObjectFromIndex:fromIndexPath.item toIndex:toIndexPath.item];
769 770
}

Carola Nitz's avatar
Carola Nitz committed
771 772 773 774 775 776
- (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]];
}

777 778
- (void)collectionView:(UICollectionView *)collectionView requestToMoveItemAtIndexPath:(NSIndexPath *)itemPath intoFolderAtIndexPath:(NSIndexPath *)folderPath
{
779 780
    id folderPathItem = [_mediaDataSource objectAtIndex:folderPath.item];
    id itemPathItem = [_mediaDataSource objectAtIndex:itemPath.item];
781 782

    BOOL validFileTypeAtFolderPath = ([folderPathItem isKindOfClass:[MLFile class]] || [folderPathItem isKindOfClass:[MLLabel class]]) && [itemPathItem isKindOfClass:[MLFile class]];
783

784
    if (!validFileTypeAtFolderPath) {
785
        VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"FOLDER_INVALID_TYPE_TITLE", nil) message:NSLocalizedString(@"FOLDER_INVALID_TYPE_MESSAGE", nil) cancelButtonTitle:nil otherButtonTitles:@[NSLocalizedString(@"BUTTON_OK", nil)]];
786 787 788 789 790 791 792 793

        alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
            [self updateViewContents];
        };
        [alert show];
        return;
    }

794
    BOOL isFolder = [folderPathItem isKindOfClass:[MLLabel class]];
795 796

    if (isFolder){
797 798
        MLLabel *folder = folderPathItem;
        MLFile *file = itemPathItem;
799
        [file setLabels:[[NSSet alloc] initWithObjects:folder, nil]];
800
        file.folderTrackNumber = @([folder.files count] - 1);
801
        [_mediaDataSource removeObjectAtIndex:itemPath.item];
802 803
        [self updateViewContents];
    } else {
804
        _folderObject = folderPathItem;
805 806 807 808 809
        _indexPaths = [NSMutableArray arrayWithArray:@[itemPath]];
        [self showCreateFolderAlert];
    }
}

810 811 812 813 814 815 816 817 818 819 820
#pragma mark - collection/tableView helper
- (NSArray *)selectedIndexPaths
{
    NSArray *indexPaths;
    if (self.usingTableViewToShowData)
        indexPaths = [self.tableView indexPathsForSelectedRows];
    else
        indexPaths = [self.collectionView indexPathsForSelectedItems];

    return indexPaths ?: [NSArray array];
}
821 822 823 824
#pragma mark - Folder implementation

- (void)showCreateFolderAlert
{
825
    VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"FOLDER_CHOOSE_NAME_TITLE", nil) message:NSLocalizedString(@"FOLDER_CHOOSE_NAME_MESSAGE", nil) cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", nil) otherButtonTitles:@[NSLocalizedString(@"BUTTON_SAVE", nil)]];
826
    [alert setAlertViewStyle:UIAlertViewStylePlainTextInput];
827 828 829
    UITextField *zeroTextField = [alert textFieldAtIndex:0];
    [zeroTextField setText:NSLocalizedString(@"FOLDER_NAME_PLACEHOLDER", nil)];
    [zeroTextField setClearButtonMode:UITextFieldViewModeAlways];
830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848

    __weak VLCAlertView *weakAlert = alert;
    alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
        if (cancelled)
            [self updateViewContents];
        else
            [self createFolderWithName:[weakAlert textFieldAtIndex:0].text];
    };
    [alert show];
}

- (void)createFolder
{
    if (_libraryMode == VLCLibraryModeCreateFolder) {
        _libraryMode = _previousLibraryMode;
        [self updateViewContents];
        [self showCreateFolderAlert];
        return;
    }
849 850

    _indexPaths = [NSMutableArray arrayWithArray:[self selectedIndexPaths]];
851

852 853
    for (NSInteger i = _indexPaths.count - 1; i >=0; i--) {
        NSIndexPath *path = _indexPaths[i];
854
        id mediaObject = [_mediaDataSource objectAtIndex:path.row];
855
        if ([mediaObject isKindOfClass:[MLLabel class]])
856
            [_indexPaths removeObject:path];
857 858 859 860 861 862
    }

    if ([_indexPaths count] != 0) {
        NSArray *folder = [MLLabel allLabels];
        //if we already have folders display them
        if ([folder count] > 0) {
Carola Nitz's avatar
Carola Nitz committed
863
            [_mediaDataSource updateContentsForSelection:nil];
864
            [_mediaDataSource addAllFolders];
865
            self.title = NSLocalizedString(@"SELECT_FOLDER", nil);
866 867 868 869 870 871 872 873 874 875 876 877
            _previousLibraryMode = _libraryMode;
            _libraryMode = VLCLibraryModeCreateFolder;
            [self reloadViews];
            return;
        }
    }
    //no selected items or no existing folder ask for foldername
    [self showCreateFolderAlert];
}

- (void)removeFromFolder
{
878
    _indexPaths = [NSMutableArray arrayWithArray:[self selectedIndexPaths]];
879 880 881

    [_indexPaths sortUsingSelector:@selector(compare:)];

882
    for (NSInteger i = [_indexPaths count] - 1; i >= 0; i--) {
883
        NSIndexPath *path = _indexPaths[i];
884
        id item = [_mediaDataSource objectAtIndex:path.row];
885

886 887
        [_mediaDataSource removeMediaObjectFromFolder:item];
        [_mediaDataSource removeObjectAtIndex:path.row];
888 889 890 891 892 893 894 895 896 897 898
    }
    [self reloadViews];
}

- (void)createFolderWithName:(NSString *)folderName
{
    NSArray *labels = [MLLabel allLabels];
    for (MLLabel *label in labels){
        if ([label.name isEqualToString:folderName]) {
            _folderObject = nil;
            _indexPaths = nil;
899
            VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"FOLDER_NAME_DUPLICATE_TITLE", nil) message:NSLocalizedString(@"FOLDER_NAME_DUPLICATE_MESSAGE", nil) cancelButtonTitle:nil otherButtonTitles:@[NSLocalizedString(@"BUTTON_OK", nil)]];
900 901 902 903 904 905 906 907 908 909

            alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
                [self updateViewContents];
            };
            [alert show];
            return;
        }
    }

    if (_folderObject != nil) {
910
        id mediaObject;
911
        NSUInteger folderIndex = [_mediaDataSource indexOfObject:_folderObject];
912 913 914
        if (folderIndex != NSNotFound) {
            mediaObject = [_mediaDataSource objectAtIndex:folderIndex];
        }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
915

916
        //item got dragged onto item
Felix Paul Kühne's avatar
Felix Paul Kühne committed
917 918
        if ([mediaObject isKindOfClass:[MLFile class]]) {
            MLFile *file = (MLFile *)mediaObject;
919 920
            MLLabel *label = [[MLMediaLibrary sharedMediaLibrary] createObjectForEntity:@"Label"];
            label.name = folderName;
921

922
            file.labels = [NSSet setWithObjects:label,nil];
923
            NSNumber *folderTrackNumber = [NSNumber numberWithInt:(int)[label files].count - 1];
924
            file.folderTrackNumber = folderTrackNumber;
925

926 927 928
            [_mediaDataSource removeObjectAtIndex:folderIndex];
            [_mediaDataSource insertObject:label atIndex:folderIndex];
            id item = [_mediaDataSource objectAtIndex:((NSIndexPath *)_indexPaths[0]).item];
929

930 931 932 933
            if (![item isKindOfClass:[MLFile class]])
                return;

            MLFile *itemFile = (MLFile *)item;
934
            itemFile.labels = file.labels;
935 936
            [_mediaDataSource removeObjectAtIndex:((NSIndexPath *)_indexPaths[0]).item];

937
            itemFile.folderTrackNumber = @([label files].count - 1);
938 939
        } else {
            //item got dragged onto folder or items should be added to folder
940 941 942 943
            id label = [_mediaDataSource objectAtIndex:folderIndex];
            if (![label isKindOfClass:[MLLabel class]])
                return;

944 945
            [_indexPaths sortUsingSelector:@selector(compare:)];

946 947 948 949 950 951 952 953 954 955
            NSUInteger count = [_mediaDataSource numberOfFiles];
            for (NSInteger i = [_indexPaths count] - 1; i >= 0; i--) {
                NSIndexPath *path = _indexPaths[i];
                if (path.row >= count)
                    continue;
                id object = [_mediaDataSource objectAtIndex:path.row];
                if (_libraryMode != VLCLibraryModeCreateFolder && ![object isKindOfClass:[MLFile class]])
                    continue;
                if (_libraryMode == VLCLibraryModeCreateFolder)
                    [self updateViewContents];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
956

957
                id item = [_mediaDataSource objectAtIndex:path.row];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
958

959 960
                if (![item isKindOfClass:[MLFile class]])
                    continue;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
961

962 963 964
                MLFile *file = (MLFile *)item;
                file.labels = [NSSet setWithObjects:label, nil];
                [_mediaDataSource removeObjectAtIndex:path.row];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
965

966
                file.folderTrackNumber = @([label files].count - 1);