VLCLibraryViewController.m 74.8 KB
Newer Older
1
/*****************************************************************************
2
 * VLCLibraryViewController.m
3 4
 * VLC for iOS
 *****************************************************************************
5
 * Copyright (c) 2013-2016 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
#import "VLCMovieViewController.h"
19
#import "VLCPlaylistTableViewCell.h"
20
#import "VLCPlaylistCollectionViewCell.h"
21
#import "NSString+SupportedMedia.h"
22
#import "VLCBugreporter.h"
23
#import "VLCAppDelegate.h"
24
#import "VLCFirstStepsViewController.h"
25 26
#import "VLCFolderCollectionViewFlowLayout.h"
#import "LXReorderableCollectionViewFlowLayout.h"
27
#import "VLCOpenInActivity.h"
28
#import "VLCNavigationController.h"
29
#import "VLCPlaybackController+MediaLibrary.h"
30
#import "VLCKeychainCoordinator.h"
31 32

#import <AssetsLibrary/AssetsLibrary.h>
33
#import <CoreSpotlight/CoreSpotlight.h>
34

35 36
/* prefs keys */
static NSString *kDisplayedFirstSteps = @"Did we display the first steps tutorial?";
37
static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
38
@implementation EmptyLibraryView
39 40 41 42

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

48 49
@end

50
@interface VLCLibraryViewController () <VLCFolderCollectionViewDelegateFlowLayout, LXReorderableCollectionViewDataSource, LXReorderableCollectionViewDelegateFlowLayout, UITableViewDataSource, UITableViewDelegate, MLMediaLibrary, VLCMediaListDelegate, UISearchBarDelegate, UISearchDisplayDelegate> {
Felix Paul Kühne's avatar
Felix Paul Kühne committed
51
    NSMutableArray *_foundMedia;
52
    VLCLibraryMode _libraryMode;
53
    VLCLibraryMode _previousLibraryMode;
54
    UIBarButtonItem *_menuButton;
55 56 57 58
    NSMutableArray *_indexPaths;
    id _folderObject;
    VLCFolderCollectionViewFlowLayout *_folderLayout;
    LXReorderableCollectionViewFlowLayout *_reorderLayout;
59
    BOOL _inFolder;
60
    UILongPressGestureRecognizer *_longPressGestureRecognizer;
61
    UITapGestureRecognizer *_tapTwiceGestureRecognizer;
62 63 64 65

    NSMutableArray *_searchData;
    UISearchBar *_searchBar;
    UISearchDisplayController *_searchDisplayController;
66

67 68 69
    UIBarButtonItem *_createFolderBarButtonItem;
    UIBarButtonItem *_openInActivityBarButtonItem;
    UIBarButtonItem *_removeFromFolderBarButtonItem;
70
    VLCOpenInActivity *_openInActivity;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
71
}
72

73
@property (nonatomic, strong) UIBarButtonItem *displayModeBarButtonItem;
74
@property (nonatomic, strong) UITableView *tableView;
75
@property (nonatomic, strong) UICollectionView *collectionView;
76
@property (nonatomic, strong) EmptyLibraryView *emptyLibraryView;
77
@property (nonatomic) BOOL usingTableViewToShowData;
78

Felix Paul Kühne's avatar
Felix Paul Kühne committed
79 80
@end

81
@implementation VLCLibraryViewController
Felix Paul Kühne's avatar
Felix Paul Kühne committed
82

83 84 85 86 87
- (void)dealloc
{
    [_tapTwiceGestureRecognizer removeTarget:self action:NULL];
}

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

95 96
- (void)loadView
{
97
    [self setupContentViewWithContentInset:NO];
98
    [self updateViewsForCurrentDisplayMode];
99
    _libraryMode = VLCLibraryModeAllFiles;
100

101 102 103 104 105 106 107
    self.emptyLibraryView = [[[NSBundle mainBundle] loadNibNamed:@"VLCEmptyLibraryView" owner:self options:nil] lastObject];
    _emptyLibraryView.emptyLibraryLongDescriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
    _emptyLibraryView.emptyLibraryLongDescriptionLabel.numberOfLines = 0;
}

- (void)setupContentViewWithContentInset:(BOOL)setInset
{
Tobias's avatar
Tobias committed
108
    CGRect viewDimensions = [UIApplication sharedApplication].keyWindow.bounds;
109 110 111 112
    UIView *contentView = [[UIView alloc] initWithFrame:viewDimensions];
    contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
    contentView.backgroundColor = [UIColor VLCDarkBackgroundColor];

113
    if (self.usingTableViewToShowData) {
114
        if(!_tableView) {
115
            _tableView = [[UITableView alloc] initWithFrame:viewDimensions style:UITableViewStylePlain];
116
            _tableView.backgroundColor = [UIColor VLCDarkBackgroundColor];
117 118 119 120 121
            CGRect frame = _tableView.bounds;
            frame.origin.y = -frame.size.height;
            UIView *topView = [[UIView alloc] initWithFrame:frame];
            topView.backgroundColor = [UIColor VLCDarkBackgroundColor];
            [_tableView addSubview:topView];
122 123 124 125 126 127
            _tableView.rowHeight = [VLCPlaylistTableViewCell heightOfCell];
            _tableView.separatorColor = [UIColor VLCDarkBackgroundColor];
            _tableView.delegate = self;
            _tableView.dataSource = self;
            _tableView.opaque = YES;
            _tableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
128
            _tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
129
        }
130
        _tableView.frame = contentView.bounds;
131
        [contentView addSubview:_tableView];
132
        [_tableView reloadData];
133
    } else {
134 135
        if (!_collectionView) {
            _folderLayout = [[VLCFolderCollectionViewFlowLayout alloc] init];
136
            _collectionView = [[UICollectionView alloc] initWithFrame:viewDimensions collectionViewLayout:_folderLayout];
137 138 139 140 141 142
            _collectionView.alwaysBounceVertical = YES;
            _collectionView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
            _collectionView.delegate = self;
            _collectionView.dataSource = self;
            _collectionView.opaque = YES;
            _collectionView.backgroundColor = [UIColor VLCDarkBackgroundColor];
143
            _collectionView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
144 145
            _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_collectionViewHandleLongPressGesture:)];
            [_collectionView addGestureRecognizer:_longPressGestureRecognizer];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
146
            [_collectionView registerNib:[UINib nibWithNibName:@"VLCPlaylistCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"PlaylistCell"];
147
        }
148
        _collectionView.frame = contentView.bounds;
149
        [contentView addSubview:_collectionView];
150
        [_collectionView reloadData];
151 152
    }

153
    if (setInset) {
154 155 156 157 158
        CGSize statusBarSize = [UIApplication sharedApplication].statusBarFrame.size;
        // Status bar frame doesn't change correctly on rotation
        CGFloat statusBarHeight = MIN(statusBarSize.height, statusBarSize.width);
        CGFloat originY = self.navigationController.navigationBar.frame.size.height + statusBarHeight;

159
        UIScrollView *playlistView = self.usingTableViewToShowData ? _tableView : _collectionView;
160
        playlistView.contentInset = UIEdgeInsetsMake(originY, 0, 0, 0);
161
    }
162
    self.view = contentView;
163 164 165 166
}

#pragma mark -

Felix Paul Kühne's avatar
Felix Paul Kühne committed
167 168 169
- (void)viewDidLoad
{
    [super viewDidLoad];
170
    self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", nil);
171 172
    _menuButton = [UIBarButtonItem themedRevealMenuButtonWithTarget:self andSelector:@selector(leftButtonAction:)];
    self.navigationItem.leftBarButtonItem = _menuButton;
173

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

177 178
    _emptyLibraryView.emptyLibraryLabel.text = NSLocalizedString(@"EMPTY_LIBRARY", nil);
    _emptyLibraryView.emptyLibraryLongDescriptionLabel.text = NSLocalizedString(@"EMPTY_LIBRARY_LONG", nil);
179
    [_emptyLibraryView.emptyLibraryLongDescriptionLabel sizeToFit];
180
    [_emptyLibraryView.learnMoreButton setTitle:NSLocalizedString(@"BUTTON_LEARN_MORE", nil) forState:UIControlStateNormal];
181
    _createFolderBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemOrganize target:self action:@selector(createFolder)];
182 183

    // Better visual alignment with the action button
184 185 186 187 188 189 190
    _createFolderBarButtonItem.imageInsets = UIEdgeInsetsMake(4, 0, -4, 0);
    _createFolderBarButtonItem.landscapeImagePhoneInsets = UIEdgeInsetsMake(3, 0, -3, 0);

    _removeFromFolderBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemReply target:self action:@selector(removeFromFolder)];
    _removeFromFolderBarButtonItem.imageInsets = UIEdgeInsetsMake(4, 0, -4, 0);
    _removeFromFolderBarButtonItem.landscapeImagePhoneInsets = UIEdgeInsetsMake(3, 0, -3, 0);
    _removeFromFolderBarButtonItem.enabled = NO;
191

192 193
    _openInActivityBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(actOnSelection:)];
    _openInActivityBarButtonItem.enabled = NO;
194 195 196

    UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    fixedSpace.width = 20;
197 198
    UIBarButtonItem *secondFixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    fixedSpace.width = 20;
199 200 201 202 203

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

204 205 206 207 208 209 210 211 212
    [self setToolbarItems:@[_openInActivityBarButtonItem,
                            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,
                            secondFixedSpace,
                            [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteSelection)]]];
213
    self.navigationController.toolbar.barStyle = UIBarStyleBlack;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
214 215
    self.navigationController.toolbar.tintColor = [UIColor whiteColor];
    [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
216

217 218
    _searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
    UINavigationBar *navBar = self.navigationController.navigationBar;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
219 220 221
    _searchBar.barTintColor = navBar.barTintColor;
    // cancel button tint color of UISearchBar with white color
    [[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIColor whiteColor], NSForegroundColorAttributeName, nil] forState:UIControlStateNormal];
222 223 224 225 226 227 228 229 230 231
    _searchBar.tintColor = navBar.tintColor;
    _searchBar.translucent = navBar.translucent;
    _searchBar.opaque = navBar.opaque;
    _searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:_searchBar contentsController:self];
    _searchDisplayController.delegate = self;
    _searchDisplayController.searchResultsDataSource = self;
    _searchDisplayController.searchResultsDelegate = self;
    _searchDisplayController.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    _searchDisplayController.searchResultsTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
    _searchBar.delegate = self;
232
    _searchBar.hidden = YES;
233

234 235 236
    _tapTwiceGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self  action:@selector(tapTwiceGestureAction:)];
    [_tapTwiceGestureRecognizer setNumberOfTapsRequired:2];
    [self.navigationController.navigationBar addGestureRecognizer:_tapTwiceGestureRecognizer];
237

238 239 240
    @synchronized (self) {
        _searchData = [[NSMutableArray alloc] init];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
241 242
}

243 244 245
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
246
    [self.collectionView.collectionViewLayout invalidateLayout];
247
    [self _displayEmptyLibraryViewIfNeeded];
248 249
}

250 251
- (void)viewDidAppear:(BOOL)animated
{
252 253
    [super viewDidAppear:animated];

254 255 256 257 258 259 260
    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];
        [defaults synchronize];
    }

261
    if (_foundMedia.count < 1)
262
        [self updateViewContents];
263 264 265 266 267 268 269 270 271
    [[MLMediaLibrary sharedMediaLibrary] performSelector:@selector(libraryDidAppear) withObject:nil afterDelay:1.];
}

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

272 273 274 275 276 277 278 279 280 281 282
- (BOOL)canBecomeFirstResponder
{
    return YES;
}

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

283 284
- (void)openMediaObject:(NSManagedObject *)mediaObject
{
285 286 287 288
    if ([mediaObject isKindOfClass:[MLAlbum class]] || [mediaObject isKindOfClass:[MLShow class]]) {

        BOOL isAlbum = [mediaObject isKindOfClass:[MLAlbum class]];
        NSArray* array =  isAlbum ? [(MLAlbum *)mediaObject sortedTracks] : [(MLShow *)mediaObject sortedEpisodes];
289
        @synchronized(_foundMedia) {
290
            _foundMedia = [NSMutableArray arrayWithArray:array];
291
        }
292
        self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedBackButtonWithTarget:self andSelector:@selector(backToAllItems:)];
293
        if (_libraryMode == VLCLibraryModeAllFiles)
294
            self.navigationItem.leftBarButtonItem.title = NSLocalizedString(@"BUTTON_BACK", nil);
295
        else
296
            [self.navigationItem.leftBarButtonItem setTitle: isAlbum ? NSLocalizedString(@"LIBRARY_MUSIC", nil) : NSLocalizedString(@"LIBRARY_SERIES", nil)];
297 298
        self.title = [(MLAlbum*)mediaObject name];
        [self reloadViews];
299 300
    } else if ([mediaObject isKindOfClass:[MLLabel class]]) {
        MLLabel *folder = (MLLabel*) mediaObject;
301
        _inFolder = YES;
302
        if (!self.usingTableViewToShowData) {
303
            if (![self.collectionView.collectionViewLayout isEqual:_reorderLayout]) {
304
                for (UIGestureRecognizer *recognizer in _collectionView.gestureRecognizers) {
305 306 307 308 309
                    if (recognizer == _folderLayout.panGestureRecognizer || recognizer == _folderLayout.longPressGestureRecognizer || recognizer == _longPressGestureRecognizer)
                        [self.collectionView removeGestureRecognizer:recognizer];
                }
                _reorderLayout = [[LXReorderableCollectionViewFlowLayout alloc] init];
                [self.collectionView setCollectionViewLayout:_reorderLayout animated:NO];
310
            }
311
        }
312
        _libraryMode = VLCLibraryModeFolder;
313
        @synchronized(_foundMedia) {
314 315
            _foundMedia = [NSMutableArray arrayWithArray:[folder sortedFolderItems]];
        }
316
        self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedBackButtonWithTarget:self andSelector:@selector(backToAllItems:)];
317
        self.navigationItem.leftBarButtonItem.title = NSLocalizedString(@"BUTTON_BACK", nil);
318 319
        self.title = [folder name];

320 321
        _removeFromFolderBarButtonItem.enabled = YES;
        _createFolderBarButtonItem.enabled = NO;
322 323 324

        [self reloadViews];
        return;
325 326 327
    } else {
        VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
        [vpc playMediaLibraryObject:mediaObject];
328 329 330 331 332 333 334

        [self createSpotlightItem:mediaObject];
    }
}

- (void)createSpotlightItem:(nonnull NSManagedObject *)mediaObject
{
335
    if ([CSSearchableItemAttributeSet class] != nil && ![[VLCKeychainCoordinator defaultCoordinator] passcodeLockEnabled]) {
336
        self.userActivity = [[NSUserActivity alloc] initWithActivityType:kVLCUserActivityPlaying];
337 338

        MLFile *file = nil;
339
        if ([mediaObject isKindOfClass:[MLAlbumTrack class]]) {
340
            file = [(MLAlbumTrack *)mediaObject anyFileFromTrack];
341
        } else if ([mediaObject isKindOfClass:[MLShowEpisode class]]) {
342 343 344
            file = [(MLShowEpisode *)mediaObject anyFileFromEpisode];
        } else if ([mediaObject isKindOfClass:[MLFile class]]){
            file = (MLFile *)mediaObject;
345
        }
346 347 348 349
        self.userActivity.title = file.title;
        self.userActivity.contentAttributeSet = file.coreSpotlightAttributeSet;
        self.userActivity.userInfo = @{@"playingmedia":mediaObject.objectID.URIRepresentation};

350 351 352 353
        self.userActivity.eligibleForSearch = YES;
        self.userActivity.eligibleForHandoff = YES;
        //self.userActivity.contentUserAction = NSUserActivityContentUserActionPlay;
        [self.userActivity becomeCurrent];
354
    }
355 356
}

357
- (void)removeMediaObject:(id)managedObject updateDatabase:(BOOL)updateDB
358 359 360 361 362 363 364 365 366 367 368 369
{
        // delete all tracks from an album
    if ([managedObject isKindOfClass:[MLAlbum class]]) {
        MLAlbum *album = managedObject;
        NSSet *iterAlbumTrack = [NSSet setWithSet:album.tracks];

        for (MLAlbumTrack *track in iterAlbumTrack) {
            NSSet *iterFiles = [NSSet setWithSet:track.files];

            for (MLFile *file in iterFiles)
                [self _deleteMediaObject:file];
        }
370
        [[MLMediaLibrary sharedMediaLibrary] removeObject: album];
371 372 373 374 375 376 377 378 379 380 381
        // delete all episodes from a show
    } else if ([managedObject isKindOfClass:[MLShow class]]) {
        MLShow *show = managedObject;
        NSSet *iterShowEpisodes = [NSSet setWithSet:show.episodes];

        for (MLShowEpisode *episode in iterShowEpisodes) {
            NSSet *iterFiles = [NSSet setWithSet:episode.files];

            for (MLFile *file in iterFiles)
                [self _deleteMediaObject:file];
        }
382
        [[MLMediaLibrary sharedMediaLibrary] removeObject: show];
383 384 385 386 387 388 389 390
        // delete all files from an episode
    } else if ([managedObject isKindOfClass:[MLShowEpisode class]]) {
        MLShowEpisode *episode = managedObject;
        NSSet *iterFiles = [NSSet setWithSet:episode.files];

        for (MLFile *file in iterFiles)
            [self _deleteMediaObject:file];
        // delete all files from a track
391
        [[MLMediaLibrary sharedMediaLibrary] removeObject: episode];
392 393 394 395 396 397
    } else if ([managedObject isKindOfClass:[MLAlbumTrack class]]) {
        MLAlbumTrack *track = managedObject;
        NSSet *iterFiles = [NSSet setWithSet:track.files];

        for (MLFile *file in iterFiles)
            [self _deleteMediaObject:file];
398 399 400 401 402 403 404 405 406
    } else if ([managedObject isKindOfClass:[MLLabel class]]) {
        MLLabel *folder = managedObject;
        NSSet *iterFiles = [NSSet setWithSet:folder.files];
        [folder removeFiles:folder.files];
        for (MLFile *file in iterFiles)
            [self _deleteMediaObject:file];
        [[MLMediaLibrary sharedMediaLibrary] removeObject:folder];
    }
    else
407
        [self _deleteMediaObject:managedObject];
408

409 410 411 412
    if (updateDB) {
        [[MLMediaLibrary sharedMediaLibrary] updateMediaDatabase];
        [self updateViewContents];
    }
413 414 415
}

- (void)_deleteMediaObject:(MLFile *)mediaObject
416
{
417
    if (_inFolder)
418 419
        [self rearrangeFolderTrackNumbersForRemovedItem:mediaObject];

420 421 422 423 424 425 426 427 428 429
    /* stop playback if needed */
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    if (vpc.isPlaying) {
        MLFile *currentlyPlayingFile = [[MLFile fileForURL:vpc.mediaPlayer.media.url] firstObject];
        if (currentlyPlayingFile) {
            if (currentlyPlayingFile == mediaObject)
                [vpc stopPlayback];
        }
    }

430
    NSFileManager *fileManager = [NSFileManager defaultManager];
431
    NSString *folderLocation = [[mediaObject.url path] stringByDeletingLastPathComponent];
432
    NSArray *allfiles = [fileManager contentsOfDirectoryAtPath:folderLocation error:nil];
433
    NSString *fileName = [mediaObject.path.lastPathComponent stringByDeletingPathExtension];
434 435
    if (!fileName)
        return;
436 437 438
    NSIndexSet *indexSet = [allfiles indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
       return ([obj rangeOfString:fileName].location != NSNotFound);
    }];
439
    NSUInteger count = indexSet.count;
440 441 442 443 444 445 446 447
    NSString *additionalFilePath;
    NSUInteger currentIndex = [indexSet firstIndex];
    for (unsigned int x = 0; x < count; x++) {
        additionalFilePath = allfiles[currentIndex];
        if ([additionalFilePath isSupportedSubtitleFormat])
            [fileManager removeItemAtPath:[folderLocation stringByAppendingPathComponent:additionalFilePath] error:nil];
        currentIndex = [indexSet indexGreaterThanIndex:currentIndex];
    }
448
    [fileManager removeItemAtURL:mediaObject.url error:nil];
449 450 451 452
}

- (void)_displayEmptyLibraryViewIfNeeded
{
453 454 455
    if (self.emptyLibraryView.superview)
        [self.emptyLibraryView removeFromSuperview];

Gleb Pinigin's avatar
Gleb Pinigin committed
456
    if (_foundMedia.count == 0) {
457 458 459 460
        _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
461
        self.emptyLibraryView.frame = self.view.bounds;
462
        [self.view addSubview:self.emptyLibraryView];
463
        self.navigationItem.rightBarButtonItems = nil;
464 465 466 467
    } else {
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
            UIBarButtonItem *toggleDisplayedView = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"tableViewIcon"] style:UIBarButtonItemStylePlain target:self action:@selector(toggleDisplayedView:)];
            self.navigationItem.rightBarButtonItems = @[toggleDisplayedView, self.editButtonItem];
468
            self.displayModeBarButtonItem = toggleDisplayedView;
469 470 471 472
        } else {
            self.navigationItem.rightBarButtonItem = self.editButtonItem;
        }
    }
473
    if (self.usingTableViewToShowData)
474 475
        _tableView.separatorStyle = (_foundMedia.count > 0)? UITableViewCellSeparatorStyleSingleLine:
                                                             UITableViewCellSeparatorStyleNone;
476
    else
477
        [self.collectionView.collectionViewLayout invalidateLayout];
478 479

    [self updateViewsForCurrentDisplayMode];
480 481
}

482 483
- (void)libraryUpgradeComplete
{
484
    self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", nil);
Gleb Pinigin's avatar
Gleb Pinigin committed
485
    self.navigationItem.leftBarButtonItem = _menuButton;
486
    self.emptyLibraryView.emptyLibraryLongDescriptionLabel.hidden = NO;
487
    self.emptyLibraryView.emptyLibraryLabel.text = NSLocalizedString(@"EMPTY_LIBRARY", nil);
488 489 490
    [self.emptyLibraryView.activityIndicator stopAnimating];
    [self.emptyLibraryView removeFromSuperview];

491 492 493 494
    [self updateViewContents];
}

- (void)updateViewContents
Felix Paul Kühne's avatar
Felix Paul Kühne committed
495
{
496 497 498
    @synchronized(self) {
        _foundMedia = [[NSMutableArray alloc] init];
    }
499

500 501 502 503 504
    if (![(VLCAppDelegate *)[UIApplication sharedApplication].delegate passcodeValidated]) {
        APLog(@"library is locked, won't show contents");
        return;
    }

505
    self.navigationItem.leftBarButtonItem = _menuButton;
506

507
    if (_libraryMode == VLCLibraryModeAllAlbums)
508
        self.title = NSLocalizedString(@"LIBRARY_MUSIC", nil);
509
    else if( _libraryMode == VLCLibraryModeAllSeries)
510
        self.title = NSLocalizedString(@"LIBRARY_SERIES", nil);
511
    else
512
        self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", nil);
513

514 515
    _createFolderBarButtonItem.enabled = (_libraryMode == VLCLibraryModeAllAlbums || _libraryMode == VLCLibraryModeAllSeries) ? NO : YES;
    _removeFromFolderBarButtonItem.enabled = NO;
516

517 518
    /* add all albums */
    if (_libraryMode != VLCLibraryModeAllSeries) {
519
        NSArray *rawAlbums = [MLAlbum allAlbums];
520
        for (MLAlbum *album in rawAlbums) {
521 522
            if (_libraryMode != VLCLibraryModeAllAlbums) {
                if (album.name.length > 0 && album.tracks.count > 1) {
523
                    @synchronized(_foundMedia) {
524 525 526 527 528
                        [_foundMedia addObject:album];
                    }
                }
            } else {
                if (album.name.length > 0) {
529
                    @synchronized(_foundMedia) {
530 531
                        [_foundMedia addObject:album];
                    }
532 533
                }
            }
534
        }
535 536 537 538 539
    }
    if (_libraryMode == VLCLibraryModeAllAlbums) {
        [self reloadViews];
        return;
    }
540

541 542 543
    /* add all shows */
    NSArray *rawShows = [MLShow allShows];
    for (MLShow *show in rawShows) {
544
        if (show.name.length > 0 && show.episodes.count > 1) {
545
            @synchronized(_foundMedia) {
546 547 548
                [_foundMedia addObject:show];
            }
        }
549 550 551 552 553 554
    }
    if (_libraryMode == VLCLibraryModeAllSeries) {
        [self reloadViews];
        return;
    }

555 556
    /* add all folders*/
    NSArray *allFolders = [MLLabel allLabels];
557
    for (MLLabel *folder in allFolders) {
558
        @synchronized(_foundMedia) {
559 560 561
            [_foundMedia addObject:folder];
        }
    }
562

563 564 565
    /* add all remaining files */
    NSArray *allFiles = [MLFile allFiles];
    for (MLFile *file in allFiles) {
566 567 568 569 570 571
        if (file.labels != nil) {
            @synchronized(file.labels) {
                if (file.labels.count > 0)
                    continue;
            }
        }
572

573
        if (!file.isShowEpisode && !file.isAlbumTrack) {
574
            @synchronized(_foundMedia) {
575
                [_foundMedia addObject:file];
576 577 578 579
            }
        }
        else if (file.isShowEpisode) {
            if (file.showEpisode.show.episodes.count < 2) {
580
                @synchronized(_foundMedia) {
581 582 583
                    [_foundMedia addObject:file];
                }
            }
584 585 586 587 588 589

            /* older MediaLibraryKit versions don't send a show name in a popular
             * corner case. hence, we need to work-around here and force a reload
             * afterwards as this could lead to the 'all my shows are gone' 
             * syndrome (see #10435, #10464, #10432 et al) */
            if (file.showEpisode.show.name.length == 0) {
590
                file.showEpisode.show.name = NSLocalizedString(@"UNTITLED_SHOW", nil);
591 592
                [self performSelector:@selector(updateViewContents) withObject:nil afterDelay:0.1];
            }
593
        } else if (file.isAlbumTrack) {
594
            if (file.albumTrack.album.tracks.count < 2) {
595
                @synchronized(_foundMedia) {
596 597 598
                    [_foundMedia addObject:file];
                }
            }
599
        }
600
    }
601

602
    [self reloadViews];
603
}
604

605
- (void)reloadViews
606
{
607
    // 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.
608
    if (self.usingTableViewToShowData) {
609
        [self.tableView reloadData];
610 611
        [self updateActionBarButtonItemStateWithSelectedIndexPaths:[self.tableView indexPathsForSelectedRows]];
    } else {
Felix Paul Kühne's avatar
Felix Paul Kühne committed
612
        [self.collectionView reloadData];
613
        [self updateActionBarButtonItemStateWithSelectedIndexPaths:[self.collectionView indexPathsForSelectedItems]];
614 615 616 617 618 619 620 621 622 623 624 625 626
        if (_libraryMode == VLCLibraryModeAllFiles) {
            if (self.collectionView.collectionViewLayout != _folderLayout) {
                for (UIGestureRecognizer *recognizer in _collectionView.gestureRecognizers) {
                    if (recognizer != _folderLayout.panGestureRecognizer ||
                        recognizer != _folderLayout.longPressGestureRecognizer ||
                        recognizer != _longPressGestureRecognizer)
                        [self.collectionView removeGestureRecognizer:recognizer];
                }

                [self.collectionView setCollectionViewLayout:_folderLayout animated:NO];
                [self.collectionView addGestureRecognizer:_longPressGestureRecognizer];
            }
        }
627
    }
628 629

    [self _displayEmptyLibraryViewIfNeeded];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
630 631
}

632 633
#pragma mark - Table View

Felix Paul Kühne's avatar
Felix Paul Kühne committed
634 635 636 637 638 639 640
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
641 642
    if (tableView == self.searchDisplayController.searchResultsTableView)
        return _searchData.count;
643

644
    return _foundMedia.count;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
645 646 647 648 649
}

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

652
    VLCPlaylistTableViewCell *cell = (VLCPlaylistTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
653
    if (cell == nil)
654
        cell = [VLCPlaylistTableViewCell cellWithReuseIdentifier:CellIdentifier];
655 656
    else
        [cell collapsWithAnimation:NO];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
657

658
    UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRightOnTableViewCellGestureAction:)];
659 660 661
    [swipeRight setDirection:(UISwipeGestureRecognizerDirectionRight)];
    [cell addGestureRecognizer:swipeRight];

662
    NSInteger row = indexPath.row;
663

664 665
    if (tableView == self.searchDisplayController.searchResultsTableView) {
        @synchronized (_searchData) {
666 667
            if (row < _searchData.count)
                cell.mediaObject = _searchData[row];
668 669 670
        }
    } else {
        @synchronized (_foundMedia) {
671 672
            if (row < _foundMedia.count)
                cell.mediaObject = _foundMedia[row];
673 674
        }
    }
675

Felix Paul Kühne's avatar
Felix Paul Kühne committed
676 677 678
    return cell;
}

679 680
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
681
    @synchronized(_foundMedia) {
682 683 684
        MLFile* object = _foundMedia[fromIndexPath.item];
        [_foundMedia removeObjectAtIndex:fromIndexPath.item];
        [_foundMedia insertObject:object atIndex:toIndexPath.item];
685 686
        if (![object isKindOfClass:[MLFile class]])
            return;
687 688
        object.folderTrackNumber = @(toIndexPath.item - 1);
        object = [_foundMedia objectAtIndex:fromIndexPath.item];
689 690
        if (![object isKindOfClass:[MLFile class]])
            return;
691 692
        object.folderTrackNumber = @(fromIndexPath.item - 1);
    }
693 694 695 696
}

- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
697
    return _inFolder;
698 699
}

700 701
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
702
    cell.backgroundColor = (indexPath.row % 2 == 0)? [UIColor blackColor]: [UIColor VLCDarkBackgroundColor];
703
    cell.multipleSelectionBackgroundView.backgroundColor = cell.backgroundColor;
704 705
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
706 707 708 709 710 711 712
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES;
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
713 714 715 716 717
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        NSInteger row = indexPath.row;
        if (row < _foundMedia.count)
            [self removeMediaObject: _foundMedia[row] updateDatabase:YES];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
718 719
}

720 721 722 723 724 725 726
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (tableView.isEditing) {
        [self updateActionBarButtonItemStateWithSelectedIndexPaths:[tableView indexPathsForSelectedRows]];
    }
}

727
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Felix Paul Kühne's avatar
Felix Paul Kühne committed
728
{
729 730 731 732 733 734 735 736 737 738 739 740
    if ([(VLCPlaylistTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] isExpanded]) {
        [(VLCPlaylistTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] collapsWithAnimation:YES];
        return;
    }

    NSArray *visibleCells = [tableView visibleCells];
    NSUInteger cellCount = visibleCells.count;
    for (NSUInteger x = 0; x < cellCount; x++) {
        if ([visibleCells[x] isExpanded])
            [visibleCells[x] collapsWithAnimation:NO];
    }

741 742 743 744 745 746
    if (tableView.isEditing) {
        if (_libraryMode == VLCLibraryModeCreateFolder) {
            _folderObject = _foundMedia[indexPath.row];
            _libraryMode = _previousLibraryMode;
            [self updateViewContents];
            [self createFolderWithName:nil];
747 748
        } else {
            [self updateActionBarButtonItemStateWithSelectedIndexPaths:[tableView indexPathsForSelectedRows]];
749
        }
750
        return;
751
    }
752

753
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
754
    NSManagedObject *selectedObject;
755

756 757
    NSUInteger row = indexPath.row;
    if (tableView == self.searchDisplayController.searchResultsTableView) {
758 759 760 761
        @synchronized (_searchData) {
            if (row < _searchData.count)
                selectedObject = _searchData[row];
        }
762
    } else {
763 764 765 766
        @synchronized (_foundMedia) {
            if (row < _foundMedia.count)
                selectedObject = _foundMedia[row];
        }
767
    }
768

769
    if (_searchDisplayController.active)
770
        [_searchDisplayController setActive:NO animated:NO];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
771

772 773
    if (selectedObject != nil)
        [self openMediaObject:selectedObject];
774 775
}

776
#pragma mark - Gesture Action
777
- (void)swipeRightOnTableViewCellGestureAction:(UIGestureRecognizer *)recognizer
778
{
779
    if ([[self.editButtonItem title] isEqualToString:NSLocalizedString(@"BUTTON_CANCEL", nil)])
780 781 782 783
        [self setEditing:NO animated:YES];
    else {
        [self setEditing:YES animated:YES];

784 785
        NSIndexPath *path = [self.tableView indexPathForRowAtPoint:[recognizer locationInView:self.tableView]];
        [self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:path.row inSection:path.section]
786 787
                                animated:YES
                          scrollPosition:UITableViewScrollPositionNone];
788
        [self updateActionBarButtonItemStateWithSelectedIndexPaths:[self.tableView indexPathsForSelectedRows]];
789 790 791
    }
}

792 793 794 795 796 797 798
- (void)swipeRightOnCollectionViewCellGestureAction:(UIGestureRecognizer *)recognizer
{
    NSIndexPath *path = [self.collectionView indexPathForItemAtPoint:[recognizer locationInView:self.collectionView]];
    VLCPlaylistCollectionViewCell *cell = (VLCPlaylistCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:path];
    [cell showMetadata:!cell.showsMetaData];
}

799 800
- (void)tapTwiceGestureAction:(UIGestureRecognizer *)recognizer
{
801
    if (!self.usingTableViewToShowData)
802
        return;
803

804
    _searchBar.hidden = !_searchBar.hidden;
805

806 807 808 809
    if (_searchBar.hidden)
        self.tableView.tableHeaderView = nil;
    else
        self.tableView.tableHeaderView = _searchBar;
810

811
    [self.tableView setContentOffset:CGPointMake(0.0f, -self.tableView.contentInset.top) animated:NO];
812 813
}

814 815 816 817 818 819 820 821 822
#pragma mark - Collection View
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return _foundMedia.count;
}

- (UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    VLCPlaylistCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"PlaylistCell" forIndexPath:indexPath];
823 824 825 826 827
    @synchronized (_foundMedia) {
        NSUInteger row = indexPath.row;
        if (row < _foundMedia.count)
            cell.mediaObject = _foundMedia[row];
    }
828

829 830 831 832
    cell.collectionView = _collectionView;

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

833 834 835 836
    UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRightOnCollectionViewCellGestureAction:)];
    [swipeRight setDirection:(UISwipeGestureRecognizerDirectionRight)];
    [cell addGestureRecognizer:swipeRight];

837 838 839 840 841
    return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
842
    const CGFloat maxCellWidth = 300.0;
Tobias's avatar
Tobias committed
843
    const CGFloat aspectRatio = 9.0/16.0;
844 845 846 847 848 849 850 851 852

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

    int numberOfCellsPerRow = ceil(windowWidth/maxCellWidth);
    CGFloat cellWidth = windowWidth/numberOfCellsPerRow;

    return CGSizeMake(cellWidth, cellWidth * aspectRatio);

853 854 855 856
}

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
857
    return UIEdgeInsetsZero;
858 859
}

860 861
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
862
    return 0.;
863 864 865 866
}

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

870 871
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
872
    if (self.editing) {
873 874 875
        if (_libraryMode == VLCLibraryModeCreateFolder) {
            _folderObject = _foundMedia[indexPath.item];
            [self createFolderWithName:nil];
876
             _libraryMode = _previousLibraryMode;
877 878
        } else {
            [self updateActionBarButtonItemStateWithSelectedIndexPaths:[collectionView indexPathsForSelectedItems]];
879
        }
880 881 882 883
        [(VLCPlaylistCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath] selectionUpdate];
        return;
    }

884
    [collectionView deselectItemAtIndexPath:indexPath animated:NO];
885 886
    NSArray *visibleCells = [[collectionView visibleCells] copy];
    NSUInteger cellCount = visibleCells.count;
887

888 889 890 891 892 893
    for (NSUInteger x = 0; x < cellCount; x++) {
        VLCPlaylistCollectionViewCell *cell = visibleCells[x];
        if ([cell showsMetaData])
            [cell showMetadata:NO];
    }

894
    NSManagedObject *selectedObject;
895
    NSInteger row = indexPath.row;
896
    @synchronized(_foundMedia) {
897 898
        if (row < _foundMedia.count)
            selectedObject = _foundMedia[row];
899
    }
900 901
    if (selectedObject != nil)
        [self openMediaObject:selectedObject];
902 903
}

904 905
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
906 907 908 909
    if (self.editing) {
        [self updateActionBarButtonItemStateWithSelectedIndexPaths:[collectionView indexPathsForSelectedItems]];
    }

910 911 912
    [(VLCPlaylistCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath] selectionUpdate];
}

913 914
- (void)collectionView:(UICollectionView *)collectionView removeItemFromFolderAtIndexPathIfNeeded:(NSIndexPath *)indexPath
{
915
    id mediaObject;
916
    @synchronized(_foundMedia) {
917 918
        mediaObject = _foundMedia[indexPath.item];
    }
919 920 921 922 923 924 925
    if (![mediaObject isKindOfClass:[MLFile class]])
        return;

    MLFile *mediaFile = (MLFile *)mediaObject;
    [self rearrangeFolderTrackNumbersForRemovedItem:mediaFile];
    mediaFile.labels = nil;
    mediaFile.folderTrackNumber = nil;
926 927 928 929

    [self backToAllItems:nil];
}

930 931
- (void)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath willMoveToIndexPath:(NSIndexPath *)toIndexPath
{
932
    @synchronized(_foundMedia) {
933 934 935
        id object = _foundMedia[fromIndexPath.item];
        if (![object isKindOfClass:[MLFile class]])
            return;
936 937
        [_foundMedia removeObjectAtIndex:fromIndexPath.item];
        [_foundMedia insertObject:object atIndex:toIndexPath.item];
938
        [(MLFile *)object setFolderTrackNumber: @(toIndexPath.item - 1)];
939
        object = _foundMedia[fromIndexPath.item];
940 941 942
        if (![object isKindOfClass:[MLFile class]])
            return;
        [(MLFile *)object setFolderTrackNumber: @(fromIndexPath.item - 1)];
943
    }
944 945
}

946 947
- (void)collectionView:(UICollectionView *)collectionView requestToMoveItemAtIndexPath:(NSIndexPath *)itemPath intoFolderAtIndexPath:(NSIndexPath *)folderPath
{
948 949
    id folderPathItem;
    id itemPathItem;
950
    @synchronized(_foundMedia) {
951 952 953
        folderPathItem = _foundMedia[folderPath.item];
        itemPathItem = _foundMedia[itemPath.item];
    }
954 955

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

957
    if (!validFileTypeAtFolderPath) {
958
        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)]];
959 960 961 962 963 964 965 966

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

967
    BOOL isFolder = [folderPathItem isKindOfClass:[MLLabel class]];
968 969

    if (isFolder){
970 971
        MLLabel *folder = folderPathItem;
        MLFile *file = itemPathItem;
972
        [file setLabels:[[NSSet alloc] initWithObjects:folder, nil]];
973
        file.folderTrackNumber = @([folder.files count] - 1);
974
        @synchronized(_foundMedia) {
975 976
            [_foundMedia removeObjectAtIndex:itemPath.item];
        }
977 978
        [self updateViewContents];
    } else {
979
        _folderObject = folderPathItem;
980 981 982 983 984
        _indexPaths = [NSMutableArray arrayWithArray:@[itemPath]];
        [self showCreateFolderAlert];
    }
}

985 986 987 988 989 990 991 992 993 994 995
#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];
}
996 997
#pragma mark - Folder implementation

998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
- (void)rearrangeFolderTrackNumbersForRemovedItem:(MLFile *) mediaObject
{
    MLLabel *label = [mediaObject.labels anyObject];
    NSSet *allFiles = label.files;
    for (MLFile *file in allFiles) {
        if (file.folderTrackNumber > mediaObject.folderTrackNumber) {
            int value = [file.folderTrackNumber intValue];
            file.folderTrackNumber = [NSNumber numberWithInt:value - 1];
        }
    }
}

1010 1011
- (void)showCreateFolderAlert
{
1012
    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)]];
1013
    [alert setAlertViewStyle:UIAlertViewStylePlainTextInput];
1014 1015 1016
    UITextField *zeroTextField = [alert textFieldAtIndex:0];
    [zeroTextField setText:NSLocalizedString(@"FOLDER_NAME_PLACEHOLDER", nil)];
    [zeroTextField setClearButtonMode:UITextFieldViewModeAlways];
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035

    __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;
    }
1036 1037

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

1039 1040
    for (NSInteger i = _indexPaths.count - 1; i >=0; i--) {
        NSIndexPath *path = _indexPaths[i];
1041
        id mediaObject;
1042
        @synchronized(_foundMedia) {
1043
            mediaObject = _foundMedia[path.row];
1044
        }
1045
        if ([mediaObject isKindOfClass:[MLLabel class]])
1046
            [_indexPaths removeObject:path];
1047 1048 1049 1050 1051 1052
    }

    if ([_indexPaths count] != 0) {
        NSArray *folder = [MLLabel allLabels];
        //if we already have folders display them
        if ([folder count] > 0) {
1053 1054 1055
            @synchronized(self) {
                _foundMedia = [NSMutableArray arrayWithArray:folder];
            }
1056
            self.title = NSLocalizedString(@"SELECT_FOLDER", nil);
1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068
            _previousLibraryMode = _libraryMode;
            _libraryMode = VLCLibraryModeCreateFolder;
            [self reloadViews];
            return;
        }
    }
    //no selected items or no existing folder ask for foldername
    [self showCreateFolderAlert];
}

- (void)removeFromFolder
{
1069
    _indexPaths = [NSMutableArray arrayWithArray:[self selectedIndexPaths]];
1070 1071 1072

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

1073
    for (NSInteger i = [_indexPaths count] - 1; i >= 0; i--) {
1074
        NSIndexPath *path = _indexPaths[i];
1075
        id item;
1076
        @synchronized(_foundMedia) {
1077
            item = _foundMedia[path.row];
1078
        }
1079 1080 1081 1082 1083 1084

        if ([item isKindOfClass:[MLFile class]]) {
            MLFile *file = (MLFile *)item;
            [self rearrangeFolderTrackNumbersForRemovedItem:file];
            file.labels = nil;
            file.folderTrackNumber = nil;
1085
        }
1086
        @synchronized(_foundMedia) {
1087
            [_foundMedia removeObject:item];
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
        }
    }
    [self reloadViews];
}

- (void)createFolderWithName:(NSString *)folderName
{
    NSArray *labels = [MLLabel allLabels];
    for (MLLabel *label in labels){
        if ([label.name isEqualToString:folderName]) {
            _folderObject = nil;
            _indexPaths = nil;
1100
            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)]];
1101 1102 1103 1104 1105 1106 1107 1108 1109 1110

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

    if (_folderObject != nil) {
1111 1112
        id mediaObject;
        NSUInteger folderIndex;
1113
        @synchronized(_foundMedia) {
1114 1115 1116 1117
            folderIndex = [_foundMedia indexOfObject:_folderObject];
            mediaObject = _foundMedia[folderIndex];
        }

1118
        //item got dragged onto item
1119 1120
        if ([mediaObject isKindOfClass:[MLFile class]]) {
            MLFile *file = (MLFile *)mediaObject;
1121 1122
            MLLabel *label = [[MLMediaLibrary sharedMediaLibrary] createObjectForEntity:@"Label"];
            label.name = folderName;
1123

1124
            file.labels = [NSSet setWithObjects:label,nil];
1125
            NSNumber *folderTrackNumber = [NSNumber numberWithInt:(int)[label files].count - 1];
1126
            file.folderTrackNumber = folderTrackNumber;
1127

1128
            id item;
1129
            @synchronized(_foundMedia) {
1130 1131
                [_foundMedia removeObjectAtIndex:folderIndex];
                [_foundMedia insertObject:label atIndex:folderIndex];
1132 1133

                item = _foundMedia[((NSIndexPath *)_indexPaths[0]).item];
1134
            }
1135 1136 1137 1138
            if (![item isKindOfClass:[MLFile class]])