VLCPlaylistViewController.m 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284
  1. /*****************************************************************************
  2. * VLCPlaylistViewController.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2013-2014 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Felix Paul Kühne <fkuehne # videolan.org>
  9. * Gleb Pinigin <gpinigin # gmail.com>
  10. * Tamas Timar <ttimar.vlc # gmail.com>
  11. * Carola Nitz <nitz.carola # gmail.com>
  12. *
  13. * Refer to the COPYING file of the official project for license.
  14. *****************************************************************************/
  15. #import "VLCPlaylistViewController.h"
  16. #import "VLCMovieViewController.h"
  17. #import "VLCPlaylistTableViewCell.h"
  18. #import "VLCPlaylistCollectionViewCell.h"
  19. #import "UINavigationController+Theme.h"
  20. #import "NSString+SupportedMedia.h"
  21. #import "VLCBugreporter.h"
  22. #import "VLCAppDelegate.h"
  23. #import "UIBarButtonItem+Theme.h"
  24. #import "VLCFirstStepsViewController.h"
  25. #import "VLCFolderCollectionViewFlowLayout.h"
  26. #import "LXReorderableCollectionViewFlowLayout.h"
  27. #import "VLCAlertView.h"
  28. /* prefs keys */
  29. static NSString *kDisplayedFirstSteps = @"Did we display the first steps tutorial?";
  30. @implementation EmptyLibraryView
  31. - (IBAction)learnMore:(id)sender
  32. {
  33. UIViewController *firstStepsVC = [[VLCFirstStepsViewController alloc] initWithNibName:nil bundle:nil];
  34. UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:firstStepsVC];
  35. navCon.modalPresentationStyle = UIModalPresentationFormSheet;
  36. [navCon loadTheme];
  37. [self.window.rootViewController presentViewController:navCon animated:YES completion:nil];
  38. }
  39. @end
  40. @interface VLCPlaylistViewController () <VLCFolderCollectionViewDelegateFlowLayout, LXReorderableCollectionViewDataSource, LXReorderableCollectionViewDelegateFlowLayout, UITableViewDataSource, UITableViewDelegate, MLMediaLibrary, VLCMediaListDelegate, UISearchBarDelegate, UISearchDisplayDelegate> {
  41. NSMutableArray *_foundMedia;
  42. VLCLibraryMode _libraryMode;
  43. VLCLibraryMode _previousLibraryMode;
  44. UIBarButtonItem *_menuButton;
  45. NSMutableArray *_indexPaths;
  46. id _folderObject;
  47. VLCFolderCollectionViewFlowLayout *_folderLayout;
  48. LXReorderableCollectionViewFlowLayout *_reorderLayout;
  49. BOOL inFolder;
  50. UILongPressGestureRecognizer *_longPressGestureRecognizer;
  51. NSInteger _mediaToPlayIndex;
  52. VLCMediaList *_list;
  53. NSArray *_tracks;
  54. NSMutableArray *_searchData;
  55. UISearchBar *_searchBar;
  56. UISearchDisplayController *_searchDisplayController;
  57. BOOL _usingTableViewToShowData;
  58. }
  59. @property (nonatomic, strong) UITableView *tableView;
  60. @property (nonatomic, strong) UICollectionView *collectionView;
  61. @property (nonatomic, strong) EmptyLibraryView *emptyLibraryView;
  62. @end
  63. @implementation VLCPlaylistViewController
  64. + (void)initialize
  65. {
  66. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  67. [defaults registerDefaults:@{kDisplayedFirstSteps : [NSNumber numberWithBool:NO]}];
  68. }
  69. - (void)loadView
  70. {
  71. _usingTableViewToShowData = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone;
  72. if (_usingTableViewToShowData) {
  73. _tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStylePlain];
  74. _tableView.backgroundColor = [UIColor VLCDarkBackgroundColor];
  75. _tableView.rowHeight = [VLCPlaylistTableViewCell heightOfCell];
  76. _tableView.separatorColor = [UIColor VLCDarkBackgroundColor];
  77. _tableView.delegate = self;
  78. _tableView.dataSource = self;
  79. _tableView.opaque = YES;
  80. self.view = _tableView;
  81. } else {
  82. _folderLayout = [[VLCFolderCollectionViewFlowLayout alloc] init];
  83. _collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:_folderLayout];
  84. _collectionView.alwaysBounceVertical = YES;
  85. _collectionView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
  86. _collectionView.delegate = self;
  87. _collectionView.dataSource = self;
  88. _collectionView.opaque = YES;
  89. _collectionView.backgroundColor = [UIColor VLCDarkBackgroundColor];
  90. self.view = _collectionView;
  91. _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_collectionViewHandleLongPressGesture:)];
  92. [_collectionView addGestureRecognizer:_longPressGestureRecognizer];
  93. if (SYSTEM_RUNS_IOS7_OR_LATER)
  94. [_collectionView registerNib:[UINib nibWithNibName:@"VLCFuturePlaylistCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"PlaylistCell"];
  95. else
  96. [_collectionView registerNib:[UINib nibWithNibName:@"VLCPlaylistCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"PlaylistCell"];
  97. self.view.backgroundColor = [UIColor VLCDarkBackgroundColor];
  98. }
  99. _libraryMode = VLCLibraryModeAllFiles;
  100. self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  101. self.emptyLibraryView = [[[NSBundle mainBundle] loadNibNamed:@"VLCEmptyLibraryView" owner:self options:nil] lastObject];
  102. _emptyLibraryView.emptyLibraryLongDescriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
  103. _emptyLibraryView.emptyLibraryLongDescriptionLabel.numberOfLines = 0;
  104. }
  105. #pragma mark -
  106. - (void)viewDidLoad
  107. {
  108. [super viewDidLoad];
  109. self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", @"");
  110. _menuButton = [UIBarButtonItem themedRevealMenuButtonWithTarget:self andSelector:@selector(leftButtonAction:)];
  111. self.navigationItem.leftBarButtonItem = _menuButton;
  112. if (SYSTEM_RUNS_IOS7_OR_LATER)
  113. self.editButtonItem.tintColor = [UIColor whiteColor];
  114. else {
  115. [self.editButtonItem setBackgroundImage:[UIImage imageNamed:@"button"]
  116. forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
  117. [self.editButtonItem setBackgroundImage:[UIImage imageNamed:@"buttonHighlight"]
  118. forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
  119. }
  120. _emptyLibraryView.emptyLibraryLabel.text = NSLocalizedString(@"EMPTY_LIBRARY", @"");
  121. _emptyLibraryView.emptyLibraryLongDescriptionLabel.text = NSLocalizedString(@"EMPTY_LIBRARY_LONG", @"");
  122. [_emptyLibraryView.emptyLibraryLongDescriptionLabel sizeToFit];
  123. [_emptyLibraryView.learnMoreButton setTitle:NSLocalizedString(@"BUTTON_LEARN_MORE", @"") forState:UIControlStateNormal];
  124. UIBarButtonItem *createFolderItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemOrganize target:self action:@selector(createFolder)];
  125. [self setToolbarItems:@[createFolderItem, [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil], [UIBarButtonItem themedDarkToolbarButtonWithTitle:NSLocalizedString(@"BUTTON_RENAME", @"") target:self andSelector:@selector(renameSelection)], [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil], [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteSelection)]]];
  126. self.navigationController.toolbar.barStyle = UIBarStyleBlack;
  127. if (SYSTEM_RUNS_IOS7_OR_LATER) {
  128. self.navigationController.toolbar.tintColor = [UIColor whiteColor];
  129. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
  130. } else
  131. [self.navigationController.toolbar setBackgroundImage:[UIImage imageNamed:@"bottomBlackBar"] forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
  132. if (_usingTableViewToShowData) {
  133. _searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
  134. UINavigationBar *navBar = self.navigationController.navigationBar;
  135. if (SYSTEM_RUNS_IOS7_OR_LATER) {
  136. _searchBar.barTintColor = navBar.barTintColor;
  137. }
  138. _searchBar.tintColor = navBar.tintColor;
  139. _searchBar.translucent = navBar.translucent;
  140. _searchBar.opaque = navBar.opaque;
  141. _searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:_searchBar contentsController:self];
  142. _searchDisplayController.delegate = self;
  143. _searchDisplayController.searchResultsDataSource = self;
  144. _searchDisplayController.searchResultsDelegate = self;
  145. _searchBar.delegate = self;
  146. UITapGestureRecognizer *tapTwiceGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapTwiceGestureAction:)];
  147. [tapTwiceGesture setNumberOfTapsRequired:2];
  148. [self.navigationController.navigationBar addGestureRecognizer:tapTwiceGesture];
  149. }
  150. _searchData = [[NSMutableArray alloc] init];
  151. }
  152. - (void)viewWillAppear:(BOOL)animated
  153. {
  154. [super viewWillAppear:animated];
  155. [self.collectionView.collectionViewLayout invalidateLayout];
  156. [self _displayEmptyLibraryViewIfNeeded];
  157. }
  158. - (void)viewDidAppear:(BOOL)animated
  159. {
  160. [super viewDidAppear:animated];
  161. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  162. if (![[defaults objectForKey:kDisplayedFirstSteps] boolValue]) {
  163. [self.emptyLibraryView performSelector:@selector(learnMore:) withObject:nil afterDelay:1.];
  164. [defaults setObject:[NSNumber numberWithBool:YES] forKey:kDisplayedFirstSteps];
  165. [defaults synchronize];
  166. }
  167. if ([[MLMediaLibrary sharedMediaLibrary] libraryNeedsUpgrade]) {
  168. self.navigationItem.rightBarButtonItem = nil;
  169. self.navigationItem.leftBarButtonItem = nil;
  170. self.title = @"";
  171. self.emptyLibraryView.emptyLibraryLabel.text = NSLocalizedString(@"UPGRADING_LIBRARY", @"");
  172. self.emptyLibraryView.emptyLibraryLongDescriptionLabel.hidden = YES;
  173. [self.emptyLibraryView.activityIndicator startAnimating];
  174. self.emptyLibraryView.frame = self.view.bounds;
  175. [self.view addSubview:self.emptyLibraryView];
  176. [[MLMediaLibrary sharedMediaLibrary] setDelegate: self];
  177. [[MLMediaLibrary sharedMediaLibrary] performSelectorInBackground:@selector(upgradeLibrary) withObject:nil];
  178. return;
  179. }
  180. if (_foundMedia.count < 1)
  181. [self updateViewContents];
  182. [[MLMediaLibrary sharedMediaLibrary] performSelector:@selector(libraryDidAppear) withObject:nil afterDelay:1.];
  183. }
  184. - (void)viewDidDisappear:(BOOL)animated
  185. {
  186. [super viewDidDisappear:animated];
  187. [[MLMediaLibrary sharedMediaLibrary] libraryDidDisappear];
  188. }
  189. - (BOOL)canBecomeFirstResponder
  190. {
  191. return YES;
  192. }
  193. - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
  194. {
  195. if (motion == UIEventSubtypeMotionShake)
  196. [[VLCBugreporter sharedInstance] handleBugreportRequest];
  197. }
  198. - (void)openMediaObject:(NSManagedObject *)mediaObject
  199. {
  200. if ([mediaObject isKindOfClass:[MLAlbum class]]) {
  201. _foundMedia = [NSMutableArray arrayWithArray:[(MLAlbum *)mediaObject sortedTracks]];
  202. self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedBackButtonWithTarget:self andSelector:@selector(backToAllItems:)];
  203. if (_libraryMode == VLCLibraryModeAllFiles)
  204. self.navigationItem.leftBarButtonItem.title = NSLocalizedString(@"BUTTON_BACK", @"");
  205. else
  206. [self.navigationItem.leftBarButtonItem setTitle:NSLocalizedString(@"LIBRARY_MUSIC", @"")];
  207. self.title = [(MLAlbum*)mediaObject name];
  208. [self reloadViews];
  209. } else if ([mediaObject isKindOfClass:[MLShow class]]) {
  210. _foundMedia = [NSMutableArray arrayWithArray:[(MLShow *)mediaObject sortedEpisodes]];
  211. self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedBackButtonWithTarget:self andSelector:@selector(backToAllItems:)];
  212. if (_libraryMode == VLCLibraryModeAllFiles)
  213. self.navigationItem.leftBarButtonItem.title = NSLocalizedString(@"BUTTON_BACK", @"");
  214. else
  215. [self.navigationItem.leftBarButtonItem setTitle:NSLocalizedString(@"LIBRARY_SERIES", @"")];
  216. self.title = [(MLShow*)mediaObject name];
  217. [self reloadViews];
  218. } else if ([mediaObject isKindOfClass:[MLLabel class]]) {
  219. MLLabel *folder = (MLLabel*) mediaObject;
  220. inFolder = YES;
  221. if (!_usingTableViewToShowData) {
  222. if (![self.collectionView.collectionViewLayout isEqual:_reorderLayout]) {
  223. for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers) {
  224. if (recognizer == _folderLayout.panGestureRecognizer || recognizer == _folderLayout.longPressGestureRecognizer || recognizer == _longPressGestureRecognizer)
  225. [self.collectionView removeGestureRecognizer:recognizer];
  226. }
  227. _reorderLayout = [[LXReorderableCollectionViewFlowLayout alloc] init];
  228. [self.collectionView setCollectionViewLayout:_reorderLayout animated:NO];
  229. }
  230. }
  231. _foundMedia = [NSMutableArray arrayWithArray:[folder sortedFolderItems]];
  232. self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedBackButtonWithTarget:self andSelector:@selector(backToAllItems:)];
  233. self.navigationItem.leftBarButtonItem.title = NSLocalizedString(@"BUTTON_BACK", @"");
  234. self.title = [folder name];
  235. UIBarButtonItem *removeFromFolder = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemReply target:self action:@selector(removeFromFolder)];
  236. NSMutableArray *toolbarItems = [self.toolbarItems mutableCopy];
  237. toolbarItems[0] = removeFromFolder;
  238. self.toolbarItems = toolbarItems;
  239. [self reloadViews];
  240. return;
  241. } else
  242. [(VLCAppDelegate*)[UIApplication sharedApplication].delegate openMediaFromManagedObject:mediaObject];
  243. }
  244. - (void)removeMediaObject:(id)managedObject updateDatabase:(BOOL)updateDB
  245. {
  246. // delete all tracks from an album
  247. if ([managedObject isKindOfClass:[MLAlbum class]]) {
  248. MLAlbum *album = managedObject;
  249. NSSet *iterAlbumTrack = [NSSet setWithSet:album.tracks];
  250. for (MLAlbumTrack *track in iterAlbumTrack) {
  251. NSSet *iterFiles = [NSSet setWithSet:track.files];
  252. for (MLFile *file in iterFiles)
  253. [self _deleteMediaObject:file];
  254. }
  255. // delete all episodes from a show
  256. } else if ([managedObject isKindOfClass:[MLShow class]]) {
  257. MLShow *show = managedObject;
  258. NSSet *iterShowEpisodes = [NSSet setWithSet:show.episodes];
  259. for (MLShowEpisode *episode in iterShowEpisodes) {
  260. NSSet *iterFiles = [NSSet setWithSet:episode.files];
  261. for (MLFile *file in iterFiles)
  262. [self _deleteMediaObject:file];
  263. }
  264. // delete all files from an episode
  265. } else if ([managedObject isKindOfClass:[MLShowEpisode class]]) {
  266. MLShowEpisode *episode = managedObject;
  267. NSSet *iterFiles = [NSSet setWithSet:episode.files];
  268. for (MLFile *file in iterFiles)
  269. [self _deleteMediaObject:file];
  270. // delete all files from a track
  271. } else if ([managedObject isKindOfClass:[MLAlbumTrack class]]) {
  272. MLAlbumTrack *track = managedObject;
  273. NSSet *iterFiles = [NSSet setWithSet:track.files];
  274. for (MLFile *file in iterFiles)
  275. [self _deleteMediaObject:file];
  276. } else if ([managedObject isKindOfClass:[MLLabel class]]) {
  277. MLLabel *folder = managedObject;
  278. NSSet *iterFiles = [NSSet setWithSet:folder.files];
  279. [folder removeFiles:folder.files];
  280. for (MLFile *file in iterFiles)
  281. [self _deleteMediaObject:file];
  282. [[MLMediaLibrary sharedMediaLibrary] removeObject:folder];
  283. }
  284. else
  285. [self _deleteMediaObject:managedObject];
  286. if (updateDB) {
  287. [[MLMediaLibrary sharedMediaLibrary] updateMediaDatabase];
  288. [self updateViewContents];
  289. }
  290. }
  291. - (void)_deleteMediaObject:(MLFile *)mediaObject
  292. {
  293. if (inFolder)
  294. [self rearrangeFolderTrackNumbersForRemovedItem:mediaObject];
  295. NSFileManager *fileManager = [NSFileManager defaultManager];
  296. NSString *folderLocation = [[[NSURL URLWithString:mediaObject.url] path] stringByDeletingLastPathComponent];
  297. NSArray *allfiles = [fileManager contentsOfDirectoryAtPath:folderLocation error:nil];
  298. NSString *fileName = [[[[NSURL URLWithString:mediaObject.url] path] lastPathComponent] stringByDeletingPathExtension];
  299. NSIndexSet *indexSet = [allfiles indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
  300. return ([obj rangeOfString:fileName].location != NSNotFound);
  301. }];
  302. NSUInteger count = indexSet.count;
  303. NSString *additionalFilePath;
  304. NSUInteger currentIndex = [indexSet firstIndex];
  305. for (unsigned int x = 0; x < count; x++) {
  306. additionalFilePath = allfiles[currentIndex];
  307. if ([additionalFilePath isSupportedSubtitleFormat])
  308. [fileManager removeItemAtPath:[folderLocation stringByAppendingPathComponent:additionalFilePath] error:nil];
  309. currentIndex = [indexSet indexGreaterThanIndex:currentIndex];
  310. }
  311. [fileManager removeItemAtPath:[[NSURL URLWithString:mediaObject.url] path] error:nil];
  312. }
  313. - (void)_displayEmptyLibraryViewIfNeeded
  314. {
  315. if (self.emptyLibraryView.superview)
  316. [self.emptyLibraryView removeFromSuperview];
  317. if (_foundMedia.count == 0) {
  318. self.emptyLibraryView.emptyLibraryLabel.text = inFolder ? NSLocalizedString(@"FOLDER_EMPTY", @"") : NSLocalizedString(@"EMPTY_LIBRARY", @"");
  319. self.emptyLibraryView.emptyLibraryLongDescriptionLabel.text = inFolder ? NSLocalizedString(@"FOLDER_EMPTY_LONG", @"") : NSLocalizedString(@"EMPTY_LIBRARY_LONG", @"");
  320. self.emptyLibraryView.learnMoreButton.hidden = inFolder;
  321. self.emptyLibraryView.frame = self.view.bounds;
  322. [self.view addSubview:self.emptyLibraryView];
  323. self.navigationItem.rightBarButtonItem = nil;
  324. } else
  325. self.navigationItem.rightBarButtonItem = self.editButtonItem;
  326. if (_usingTableViewToShowData)
  327. _tableView.separatorStyle = (_foundMedia.count > 0)? UITableViewCellSeparatorStyleSingleLine:
  328. UITableViewCellSeparatorStyleNone;
  329. else
  330. [self.collectionView.collectionViewLayout invalidateLayout];
  331. }
  332. - (void)libraryUpgradeComplete
  333. {
  334. self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", @"");
  335. self.navigationItem.leftBarButtonItem = _menuButton;
  336. self.emptyLibraryView.emptyLibraryLongDescriptionLabel.hidden = NO;
  337. self.emptyLibraryView.emptyLibraryLabel.text = NSLocalizedString(@"EMPTY_LIBRARY", @"");
  338. [self.emptyLibraryView.activityIndicator stopAnimating];
  339. [self.emptyLibraryView removeFromSuperview];
  340. [self updateViewContents];
  341. }
  342. - (void)libraryWasUpdated
  343. {
  344. [self updateViewContents];
  345. }
  346. - (void)updateViewContents
  347. {
  348. _foundMedia = [[NSMutableArray alloc] init];
  349. self.navigationItem.leftBarButtonItem = _menuButton;
  350. if (_libraryMode == VLCLibraryModeAllAlbums)
  351. self.title = NSLocalizedString(@"LIBRARY_MUSIC", @"");
  352. else if( _libraryMode == VLCLibraryModeAllSeries)
  353. self.title = NSLocalizedString(@"LIBRARY_SERIES", @"");
  354. else
  355. self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", @"");
  356. /* add all albums */
  357. if (_libraryMode != VLCLibraryModeAllSeries) {
  358. NSArray *rawAlbums = [MLAlbum allAlbums];
  359. for (MLAlbum *album in rawAlbums) {
  360. if (album.name.length > 0 && album.tracks.count > 1)
  361. [_foundMedia addObject:album];
  362. }
  363. }
  364. if (_libraryMode == VLCLibraryModeAllAlbums) {
  365. [self reloadViews];
  366. return;
  367. }
  368. /* add all shows */
  369. NSArray *rawShows = [MLShow allShows];
  370. for (MLShow *show in rawShows) {
  371. if (show.name.length > 0 && show.episodes.count > 1)
  372. [_foundMedia addObject:show];
  373. }
  374. if (_libraryMode == VLCLibraryModeAllSeries) {
  375. [self reloadViews];
  376. return;
  377. }
  378. /* add all folders*/
  379. NSArray *allFolders = [MLLabel allLabels];
  380. for (MLLabel *folder in allFolders)
  381. [_foundMedia addObject:folder];
  382. /* add all remaining files */
  383. NSArray *allFiles = [MLFile allFiles];
  384. for (MLFile *file in allFiles) {
  385. if (file.labels.count > 0) continue;
  386. if (!file.isShowEpisode && !file.isAlbumTrack)
  387. [_foundMedia addObject:file];
  388. else if (file.isShowEpisode) {
  389. if (file.showEpisode.show.episodes.count < 2)
  390. [_foundMedia addObject:file];
  391. /* older MediaLibraryKit versions don't send a show name in a popular
  392. * corner case. hence, we need to work-around here and force a reload
  393. * afterwards as this could lead to the 'all my shows are gone'
  394. * syndrome (see #10435, #10464, #10432 et al) */
  395. if (file.showEpisode.show.name.length == 0) {
  396. file.showEpisode.show.name = NSLocalizedString(@"UNTITLED_SHOW", @"");
  397. [self performSelector:@selector(updateViewContents) withObject:nil afterDelay:0.1];
  398. }
  399. } else if (file.isAlbumTrack) {
  400. if (file.albumTrack.album.tracks.count < 2)
  401. [_foundMedia addObject:file];
  402. }
  403. }
  404. [self reloadViews];
  405. }
  406. - (void)reloadViews
  407. {
  408. if (_usingTableViewToShowData)
  409. [self.tableView reloadData];
  410. else
  411. [self.collectionView reloadData];
  412. [self _displayEmptyLibraryViewIfNeeded];
  413. }
  414. #pragma mark - Table View
  415. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  416. {
  417. return 1;
  418. }
  419. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  420. {
  421. if (tableView == self.searchDisplayController.searchResultsTableView)
  422. return _searchData.count;
  423. return _foundMedia.count;
  424. }
  425. // Customize the appearance of table view cells.
  426. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  427. {
  428. static NSString *CellIdentifier = @"PlaylistCell";
  429. VLCPlaylistTableViewCell *cell = (VLCPlaylistTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  430. if (cell == nil)
  431. cell = [VLCPlaylistTableViewCell cellWithReuseIdentifier:CellIdentifier];
  432. UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRightGestureAction:)];
  433. [swipeRight setDirection:(UISwipeGestureRecognizerDirectionRight)];
  434. [cell addGestureRecognizer:swipeRight];
  435. NSInteger row = indexPath.row;
  436. if (tableView == self.searchDisplayController.searchResultsTableView)
  437. cell.mediaObject = _searchData[row];
  438. else
  439. cell.mediaObject = _foundMedia[row];
  440. return cell;
  441. }
  442. - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
  443. {
  444. MLFile* object = [_foundMedia objectAtIndex:fromIndexPath.item];
  445. [_foundMedia removeObjectAtIndex:fromIndexPath.item];
  446. [_foundMedia insertObject:object atIndex:toIndexPath.item];
  447. object.folderTrackNumber = @(toIndexPath.item - 1);
  448. object = [_foundMedia objectAtIndex:fromIndexPath.item];
  449. object.folderTrackNumber = @(fromIndexPath.item - 1);
  450. }
  451. - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
  452. {
  453. return inFolder;
  454. }
  455. - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
  456. {
  457. cell.backgroundColor = (indexPath.row % 2 == 0)? [UIColor blackColor]: [UIColor VLCDarkBackgroundColor];
  458. cell.multipleSelectionBackgroundView.backgroundColor = cell.backgroundColor;
  459. }
  460. - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
  461. {
  462. return YES;
  463. }
  464. - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
  465. {
  466. if (editingStyle == UITableViewCellEditingStyleDelete)
  467. [self removeMediaObject: _foundMedia[indexPath.row] updateDatabase:YES];
  468. }
  469. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  470. {
  471. if (tableView.isEditing) {
  472. if (_libraryMode == VLCLibraryModeCreateFolder) {
  473. _folderObject = _foundMedia[indexPath.row];
  474. _libraryMode = _previousLibraryMode;
  475. [self updateViewContents];
  476. [self createFolderWithName:nil];
  477. }
  478. return;
  479. }
  480. [tableView deselectRowAtIndexPath:indexPath animated:YES];
  481. NSManagedObject *selectedObject;
  482. if (tableView == self.searchDisplayController.searchResultsTableView)
  483. selectedObject = _searchData[indexPath.row];
  484. else
  485. selectedObject = _foundMedia[indexPath.row];
  486. if ([selectedObject isKindOfClass:[MLAlbumTrack class]]) {
  487. _tracks = [[(MLAlbumTrack*)selectedObject album] sortedTracks];
  488. NSUInteger count = _tracks.count;
  489. _list = [[VLCMediaList alloc] init];
  490. _list.delegate = self;
  491. MLFile *file;
  492. VLCMedia *media;
  493. for (NSInteger x = count - 1; x > -1; x--) {
  494. file = [(MLAlbumTrack*)_tracks[x] files].anyObject;
  495. media = [VLCMedia mediaWithURL: [NSURL URLWithString:file.url]];
  496. [media parse];
  497. [_list addMedia:media];
  498. }
  499. _mediaToPlayIndex = indexPath.row;
  500. } else if ([selectedObject isKindOfClass:[MLFile class]] && [((MLFile *)selectedObject).labels count] > 0) {
  501. MLLabel *folder = [((MLFile *)selectedObject).labels anyObject];
  502. _tracks = [folder sortedFolderItems];
  503. NSUInteger count = _tracks.count;
  504. _list = [[VLCMediaList alloc] init];
  505. _list.delegate = self;
  506. MLFile *file;
  507. for (NSInteger x = count - 1; x > -1; x--) {
  508. file = (MLFile *)_tracks[x];
  509. [_list addMedia:[VLCMedia mediaWithURL:[NSURL URLWithString:file.url]]];
  510. }
  511. _mediaToPlayIndex = indexPath.row;
  512. }
  513. if (_searchDisplayController.active == YES)
  514. [_searchDisplayController setActive:NO animated:YES];
  515. [self openMediaObject:selectedObject];
  516. }
  517. #pragma mark - VLCMedialistDelegate
  518. - (void)mediaList:(VLCMediaList *)aMediaList mediaAdded:(VLCMedia *)media atIndex:(NSInteger)index
  519. {
  520. if (index == _tracks.count - 1) {
  521. NSManagedObject *selectedObject = _foundMedia[_mediaToPlayIndex];
  522. [(VLCAppDelegate *)[UIApplication sharedApplication].delegate openMediaList:_list atIndex:(int)[_tracks indexOfObject:selectedObject]];
  523. }
  524. }
  525. #pragma mark - Gesture Action
  526. - (void)swipeRightGestureAction:(UIGestureRecognizer *)recognizer
  527. {
  528. if ([[self.editButtonItem title] isEqualToString:NSLocalizedString(@"BUTTON_CANCEL",@"")])
  529. [self setEditing:NO animated:YES];
  530. else {
  531. [self setEditing:YES animated:YES];
  532. NSIndexPath *path = [(UITableView *)self.view indexPathForRowAtPoint:[recognizer locationInView:self.view]];
  533. [self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:path.row inSection:path.section]
  534. animated:YES
  535. scrollPosition:UITableViewScrollPositionNone];
  536. }
  537. }
  538. - (void)tapTwiceGestureAction:(UIGestureRecognizer *)recognizer
  539. {
  540. _searchBar.hidden = !_searchBar.hidden;
  541. if (_searchBar.hidden)
  542. self.tableView.tableHeaderView = nil;
  543. else
  544. self.tableView.tableHeaderView = _searchBar;
  545. }
  546. #pragma mark - Collection View
  547. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
  548. {
  549. return _foundMedia.count;
  550. }
  551. - (UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
  552. {
  553. VLCPlaylistCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"PlaylistCell" forIndexPath:indexPath];
  554. cell.mediaObject = _foundMedia[indexPath.row];
  555. cell.collectionView = _collectionView;
  556. [cell setEditing:self.editing animated:NO];
  557. return cell;
  558. }
  559. - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
  560. {
  561. if (SYSTEM_RUNS_IOS7_OR_LATER) {
  562. if (UIDeviceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation))
  563. return CGSizeMake(341., 190.);
  564. else
  565. return CGSizeMake(384., 216.);
  566. }
  567. return CGSizeMake(298.0, 220.0);
  568. }
  569. - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
  570. {
  571. if (SYSTEM_RUNS_IOS7_OR_LATER)
  572. return UIEdgeInsetsZero;
  573. return UIEdgeInsetsMake(0.0, 34.0, 0.0, 34.0);
  574. }
  575. - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
  576. {
  577. if (SYSTEM_RUNS_IOS7_OR_LATER)
  578. return 0.;
  579. return 10.0;
  580. }
  581. - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
  582. {
  583. if (SYSTEM_RUNS_IOS7_OR_LATER)
  584. return 0.;
  585. return 10.0;
  586. }
  587. - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
  588. {
  589. if (self.editing) {
  590. if (_libraryMode == VLCLibraryModeCreateFolder) {
  591. _folderObject = _foundMedia[indexPath.item];
  592. [self createFolderWithName:nil];
  593. _libraryMode = _previousLibraryMode;
  594. }
  595. [(VLCPlaylistCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath] selectionUpdate];
  596. return;
  597. }
  598. NSManagedObject *selectedObject = _foundMedia[indexPath.row];
  599. if ([selectedObject isKindOfClass:[MLAlbumTrack class]]) {
  600. VLCMediaList *list;
  601. NSArray *tracks = [[(MLAlbumTrack*)selectedObject album] sortedTracks];
  602. NSUInteger count = tracks.count;
  603. list = [[VLCMediaList alloc] init];
  604. MLFile *file;
  605. for (NSInteger x = count - 1; x > -1; x--) {
  606. file = [(MLAlbumTrack*)tracks[x] files].anyObject;
  607. [list addMedia:[VLCMedia mediaWithURL: [NSURL URLWithString:file.url]]];
  608. }
  609. [(VLCAppDelegate*)[UIApplication sharedApplication].delegate openMediaList:list atIndex:(int)[tracks indexOfObject:selectedObject]];
  610. } else if ([selectedObject isKindOfClass:[MLFile class]] && [((MLFile *)selectedObject).labels count] > 0) {
  611. VLCMediaList *list;
  612. MLLabel *folder = [((MLFile *)selectedObject).labels anyObject];
  613. NSArray *folderTracks = [folder sortedFolderItems];
  614. NSUInteger count = folderTracks.count;
  615. list = [[VLCMediaList alloc] init];
  616. MLFile *file;
  617. for (NSInteger x = count - 1; x > -1; x--) {
  618. file = (MLFile *)folderTracks[x];
  619. [list addMedia:[VLCMedia mediaWithURL:[NSURL URLWithString:file.url]]];
  620. }
  621. [(VLCAppDelegate *)[UIApplication sharedApplication].delegate openMediaList:list atIndex:(int)[folderTracks indexOfObject:selectedObject]];
  622. } else
  623. [self openMediaObject:selectedObject];
  624. }
  625. - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
  626. {
  627. [(VLCPlaylistCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath] selectionUpdate];
  628. }
  629. - (void)collectionView:(UICollectionView *)collectionView removeItemFromFolderAtIndexPathIfNeeded:(NSIndexPath *)indexPath
  630. {
  631. MLFile *mediaObject = (MLFile *)_foundMedia[indexPath.item];
  632. [self rearrangeFolderTrackNumbersForRemovedItem:mediaObject];
  633. mediaObject.labels = nil;
  634. mediaObject.folderTrackNumber = nil;
  635. [self backToAllItems:nil];
  636. }
  637. - (void)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath willMoveToIndexPath:(NSIndexPath *)toIndexPath
  638. {
  639. MLFile* object = [_foundMedia objectAtIndex:fromIndexPath.item];
  640. [_foundMedia removeObjectAtIndex:fromIndexPath.item];
  641. [_foundMedia insertObject:object atIndex:toIndexPath.item];
  642. object.folderTrackNumber = @(toIndexPath.item - 1);
  643. object = [_foundMedia objectAtIndex:fromIndexPath.item];
  644. object.folderTrackNumber = @(fromIndexPath.item - 1);
  645. }
  646. - (void)collectionView:(UICollectionView *)collectionView requestToMoveItemAtIndexPath:(NSIndexPath *)itemPath intoFolderAtIndexPath:(NSIndexPath *)folderPath
  647. {
  648. BOOL validFileTypeAtFolderPath = ([_foundMedia[folderPath.item] isKindOfClass:[MLFile class]] || [_foundMedia[folderPath.item] isKindOfClass:[MLLabel class]]) && [_foundMedia[itemPath.item] isKindOfClass:[MLFile class]];
  649. if (!validFileTypeAtFolderPath) {
  650. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"FOLDER_INVALID_TYPE_TITLE", @"") message:NSLocalizedString(@"FOLDER_INVALID_TYPE_MESSAGE", @"") cancelButtonTitle:nil otherButtonTitles:@[NSLocalizedString(@"BUTTON_OK", @"")]];
  651. alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
  652. [self updateViewContents];
  653. };
  654. [alert show];
  655. return;
  656. }
  657. BOOL isFolder = [_foundMedia[folderPath.item] isKindOfClass:[MLLabel class]];
  658. if (isFolder){
  659. MLLabel *folder = _foundMedia[folderPath.item];
  660. MLFile *file = _foundMedia[itemPath.item];
  661. [file setLabels:[[NSSet alloc] initWithObjects:folder, nil]];
  662. file.folderTrackNumber = @([folder.files count] - 1);
  663. [_foundMedia removeObjectAtIndex:itemPath.item];
  664. [self updateViewContents];
  665. } else {
  666. _folderObject = _foundMedia[folderPath.item];
  667. _indexPaths = [NSMutableArray arrayWithArray:@[itemPath]];
  668. [self showCreateFolderAlert];
  669. }
  670. }
  671. #pragma mark - Folder implementation
  672. - (void)rearrangeFolderTrackNumbersForRemovedItem:(MLFile *) mediaObject
  673. {
  674. MLLabel *label = [mediaObject.labels anyObject];
  675. NSSet *allFiles = label.files;
  676. for (MLFile *file in allFiles) {
  677. if (file.folderTrackNumber > mediaObject.folderTrackNumber) {
  678. int value = [file.folderTrackNumber intValue];
  679. file.folderTrackNumber = [NSNumber numberWithInt:value - 1];
  680. }
  681. }
  682. }
  683. - (void)showCreateFolderAlert
  684. {
  685. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"FOLDER_CHOOSE_NAME_TITLE", @"") message:NSLocalizedString(@"FOLDER_CHOOSE_NAME_MESSAGE", @"") cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", @"") otherButtonTitles:@[NSLocalizedString(@"BUTTON_SAVE", @"")]];
  686. [alert setAlertViewStyle:UIAlertViewStylePlainTextInput];
  687. [[alert textFieldAtIndex:0] setText:NSLocalizedString(@"FOLDER_NAME_PLACEHOLDER", @"")];
  688. [[alert textFieldAtIndex:0] setClearButtonMode:UITextFieldViewModeAlways];
  689. __weak VLCAlertView *weakAlert = alert;
  690. alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
  691. if (cancelled)
  692. [self updateViewContents];
  693. else
  694. [self createFolderWithName:[weakAlert textFieldAtIndex:0].text];
  695. };
  696. [alert show];
  697. }
  698. - (void)createFolder
  699. {
  700. if (_libraryMode == VLCLibraryModeCreateFolder) {
  701. _libraryMode = _previousLibraryMode;
  702. [self updateViewContents];
  703. [self showCreateFolderAlert];
  704. return;
  705. }
  706. if (!_usingTableViewToShowData)
  707. _indexPaths = [NSMutableArray arrayWithArray:[self.collectionView indexPathsForSelectedItems]];
  708. else
  709. _indexPaths = [NSMutableArray arrayWithArray:[self.tableView indexPathsForSelectedRows]];
  710. for (NSInteger i = _indexPaths.count - 1; i >=0; i--) {
  711. NSIndexPath *path = _indexPaths[i];
  712. id mediaObject;
  713. if (!_usingTableViewToShowData)
  714. mediaObject = _foundMedia[path.item];
  715. else
  716. mediaObject = _foundMedia[path.row];
  717. if ([mediaObject isKindOfClass:[MLLabel class]])
  718. [_indexPaths removeObject:path];
  719. }
  720. if ([_indexPaths count] != 0) {
  721. NSArray *folder = [MLLabel allLabels];
  722. //if we already have folders display them
  723. if ([folder count] > 0) {
  724. _foundMedia = [NSMutableArray arrayWithArray:folder];
  725. self.title = NSLocalizedString(@"SELECT_FOLDER", @"");
  726. _previousLibraryMode = _libraryMode;
  727. _libraryMode = VLCLibraryModeCreateFolder;
  728. [self reloadViews];
  729. return;
  730. }
  731. }
  732. //no selected items or no existing folder ask for foldername
  733. [self showCreateFolderAlert];
  734. }
  735. - (void)removeFromFolder
  736. {
  737. if (!_usingTableViewToShowData)
  738. _indexPaths = [NSMutableArray arrayWithArray:[self.collectionView indexPathsForSelectedItems]];
  739. else
  740. _indexPaths = [NSMutableArray arrayWithArray:[self.tableView indexPathsForSelectedRows]];
  741. [_indexPaths sortUsingSelector:@selector(compare:)];
  742. for (NSInteger i = [_indexPaths count] - 1; i >= 0; i--) {
  743. NSIndexPath *path = _indexPaths[i];
  744. MLFile *file = (MLFile *)_foundMedia[_usingTableViewToShowData ? path.row : path.item];
  745. MLLabel *folder = [file.labels anyObject];
  746. [self rearrangeFolderTrackNumbersForRemovedItem:file];
  747. file.labels = nil;
  748. file.folderTrackNumber = nil;
  749. [_foundMedia removeObject:file];
  750. if ([folder.files count] == 0) {
  751. [self removeMediaObject:folder updateDatabase:YES];
  752. [self setEditing:NO];
  753. [self backToAllItems:nil];
  754. }
  755. }
  756. [self reloadViews];
  757. }
  758. - (void)createFolderWithName:(NSString *)folderName
  759. {
  760. NSArray *labels = [MLLabel allLabels];
  761. for (MLLabel *label in labels){
  762. if ([label.name isEqualToString:folderName]) {
  763. _folderObject = nil;
  764. _indexPaths = nil;
  765. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"FOLDER_NAME_DUPLICATE_TITLE", @"") message:NSLocalizedString(@"FOLDER_NAME_DUPLICATE_MESSAGE", @"") cancelButtonTitle:nil otherButtonTitles:@[NSLocalizedString(@"BUTTON_OK", @"")]];
  766. alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
  767. [self updateViewContents];
  768. };
  769. [alert show];
  770. return;
  771. }
  772. }
  773. if (_folderObject != nil) {
  774. NSUInteger folderIndex = [_foundMedia indexOfObject:_folderObject];
  775. //item got dragged onto item
  776. if ([_foundMedia[folderIndex] isKindOfClass:[MLFile class]]) {
  777. MLFile *file = _foundMedia[folderIndex];
  778. MLLabel *label = [[MLMediaLibrary sharedMediaLibrary] createObjectForEntity:@"Label"];
  779. label.name = folderName;
  780. file.labels = [NSSet setWithObjects:label,nil];
  781. NSNumber *folderTrackNumber = [NSNumber numberWithInt:(int)[label files].count - 1];
  782. file.folderTrackNumber = folderTrackNumber;
  783. [_foundMedia removeObjectAtIndex:folderIndex];
  784. [_foundMedia insertObject:label atIndex:folderIndex];
  785. MLFile *itemFile = _foundMedia[((NSIndexPath *)_indexPaths[0]).item];
  786. itemFile.labels = file.labels;
  787. [_foundMedia removeObjectAtIndex:((NSIndexPath *)_indexPaths[0]).item];
  788. itemFile.folderTrackNumber = @([label files].count - 1);
  789. } else {
  790. //item got dragged onto folder or items should be added to folder
  791. MLLabel *label = _foundMedia[folderIndex];
  792. [_indexPaths sortUsingSelector:@selector(compare:)];
  793. for (NSInteger i = [_indexPaths count] - 1; i >= 0; i--) {
  794. NSIndexPath *path = _indexPaths[i];
  795. if (_libraryMode != VLCLibraryModeCreateFolder && ![_foundMedia[path.row] isKindOfClass:[MLFile class]])
  796. continue;
  797. if (_libraryMode == VLCLibraryModeCreateFolder)
  798. [self updateViewContents];
  799. MLFile *file = _foundMedia[path.row];
  800. file.labels = [NSSet setWithObjects:label, nil];
  801. [_foundMedia removeObjectAtIndex:path.row];
  802. file.folderTrackNumber = @([label files].count - 1);
  803. }
  804. }
  805. _folderObject = nil;
  806. } else {
  807. //create new folder
  808. MLLabel *label = [[MLMediaLibrary sharedMediaLibrary] createObjectForEntity:@"Label"];
  809. label.name = folderName;
  810. //if items were selected
  811. if ([_indexPaths count] != 0) {
  812. [_indexPaths sortUsingSelector:@selector(compare:)];
  813. for (NSInteger i = [_indexPaths count] - 1; i >= 0; i--) {
  814. NSIndexPath *path = _indexPaths[i];
  815. if (!_usingTableViewToShowData) {
  816. MLFile *file = _foundMedia[path.item];
  817. file.labels = [NSSet setWithObjects:label, nil];
  818. file.folderTrackNumber = @([label files].count - 1);
  819. [_foundMedia removeObjectAtIndex:path.item];
  820. } else {
  821. MLFile *file = _foundMedia[path.row];
  822. file.labels = [NSSet setWithObjects:label, nil];
  823. file.folderTrackNumber = @([label files].count - 1);
  824. [_foundMedia removeObjectAtIndex:path.row];
  825. }
  826. }
  827. }
  828. }
  829. _indexPaths = nil;
  830. [self setEditing:NO];
  831. [self updateViewContents];
  832. }
  833. - (void)_collectionViewHandleLongPressGesture:(UIGestureRecognizer *) sender
  834. {
  835. [self setEditing:YES animated:YES];
  836. }
  837. #pragma mark - UI implementation
  838. - (void)setEditing:(BOOL)editing animated:(BOOL)animated
  839. {
  840. [super setEditing:editing animated:animated];
  841. UIBarButtonItem *editButton = self.editButtonItem;
  842. NSString *editImage = editing? @"doneButton": @"button";
  843. NSString *editImageHighlight = editing? @"doneButtonHighlight": @"buttonHighlight";
  844. if (SYSTEM_RUNS_IOS7_OR_LATER)
  845. editButton.tintColor = [UIColor whiteColor];
  846. else {
  847. [editButton setBackgroundImage:[UIImage imageNamed:editImage] forState:UIControlStateNormal
  848. barMetrics:UIBarMetricsDefault];
  849. [editButton setBackgroundImage:[UIImage imageNamed:editImageHighlight]
  850. forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
  851. [editButton setTitleTextAttributes: editing ? @{UITextAttributeTextShadowColor : [UIColor whiteColor], UITextAttributeTextColor : [UIColor blackColor]} : @{UITextAttributeTextShadowColor : [UIColor VLCDarkTextShadowColor], UITextAttributeTextColor : [UIColor whiteColor]} forState:UIControlStateNormal];
  852. }
  853. if (!_usingTableViewToShowData) {
  854. NSArray *visibleCells = self.collectionView.visibleCells;
  855. [visibleCells enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  856. VLCPlaylistCollectionViewCell *aCell = (VLCPlaylistCollectionViewCell*)obj;
  857. [aCell setEditing:editing animated:animated];
  858. }];
  859. self.collectionView.allowsMultipleSelection = editing;
  860. /* UIKit doesn't clear the selection automagically if we leave the editing mode
  861. * so we need to do so manually */
  862. if (!editing) {
  863. [self.collectionView addGestureRecognizer:_longPressGestureRecognizer];
  864. NSArray *selectedItems = [self.collectionView indexPathsForSelectedItems];
  865. NSUInteger count = selectedItems.count;
  866. for (NSUInteger x = 0; x < count; x++)
  867. [self.collectionView deselectItemAtIndexPath:selectedItems[x] animated:NO];
  868. } else
  869. [self.collectionView removeGestureRecognizer:_longPressGestureRecognizer];
  870. } else {
  871. self.tableView.allowsMultipleSelectionDuringEditing = editing;
  872. [self.tableView setEditing:editing animated:YES];
  873. [self.editButtonItem setTitle:editing ? NSLocalizedString(@"BUTTON_CANCEL",@"") : NSLocalizedString(@"BUTTON_EDIT", @"")];
  874. }
  875. if (_libraryMode == VLCLibraryModeCreateFolder) {
  876. _libraryMode = _previousLibraryMode;
  877. _indexPaths = nil;
  878. [self updateViewContents];
  879. }
  880. self.navigationController.toolbarHidden = !editing;
  881. }
  882. - (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
  883. {
  884. return UITableViewCellEditingStyleDelete;
  885. }
  886. - (IBAction)leftButtonAction:(id)sender
  887. {
  888. [[(VLCAppDelegate*)[UIApplication sharedApplication].delegate revealController] toggleSidebar:![(VLCAppDelegate*)[UIApplication sharedApplication].delegate revealController].sidebarShowing duration:kGHRevealSidebarDefaultAnimationDuration];
  889. if (self.isEditing)
  890. [self setEditing:NO animated:YES];
  891. }
  892. - (IBAction)backToAllItems:(id)sender
  893. {
  894. if (!_usingTableViewToShowData) {
  895. if (![self.collectionView.collectionViewLayout isEqual:_folderLayout]) {
  896. //for some reason the Gesturerecognizer block themselves if not removed manually
  897. for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers) {
  898. if (recognizer == _reorderLayout.panGestureRecognizer || recognizer == _reorderLayout.longPressGestureRecognizer)
  899. [self.collectionView removeGestureRecognizer:recognizer];
  900. }
  901. _folderLayout = [[VLCFolderCollectionViewFlowLayout alloc] init];
  902. [self.collectionView setCollectionViewLayout:_folderLayout animated:NO];
  903. [_collectionView addGestureRecognizer:_longPressGestureRecognizer];
  904. }
  905. }
  906. inFolder = NO;
  907. UIBarButtonItem *createFolderItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemOrganize target:self action:@selector(createFolder)];
  908. NSMutableArray *toolbarItems = [self.toolbarItems mutableCopy];
  909. toolbarItems[0] = createFolderItem;
  910. self.toolbarItems = toolbarItems;
  911. [self setLibraryMode:_libraryMode];
  912. [self updateViewContents];
  913. }
  914. - (void)_endEditingWithHardReset:(BOOL)hardReset
  915. {
  916. [[MLMediaLibrary sharedMediaLibrary] updateMediaDatabase];
  917. if (hardReset)
  918. [self updateViewContents];
  919. else
  920. [self reloadViews];
  921. [self setEditing:NO animated:YES];
  922. }
  923. - (void)deleteSelection
  924. {
  925. NSArray *indexPaths;
  926. if (!_usingTableViewToShowData)
  927. indexPaths = [self.collectionView indexPathsForSelectedItems];
  928. else
  929. indexPaths = [self.tableView indexPathsForSelectedRows];
  930. NSUInteger count = indexPaths.count;
  931. NSMutableArray *objects = [[NSMutableArray alloc] initWithCapacity:count];
  932. for (NSUInteger x = 0; x < count; x++)
  933. [objects addObject:_foundMedia[[indexPaths[x] row]]];
  934. for (NSUInteger x = 0; x < count; x++)
  935. [self removeMediaObject:objects[x] updateDatabase:NO];
  936. [self _endEditingWithHardReset:YES];
  937. }
  938. - (void)renameSelection
  939. {
  940. NSArray *indexPaths;
  941. if (!_usingTableViewToShowData)
  942. indexPaths = [self.collectionView indexPathsForSelectedItems];
  943. else
  944. indexPaths = [self.tableView indexPathsForSelectedRows];
  945. if (indexPaths.count < 1) {
  946. [self _endEditingWithHardReset:NO];
  947. return;
  948. }
  949. NSString *itemName;
  950. if (!_usingTableViewToShowData)
  951. itemName = [(VLCPlaylistCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPaths[0]] titleLabel].text;
  952. else
  953. itemName = [(VLCPlaylistTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPaths[0]] titleLabel].text;
  954. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:[NSString stringWithFormat:NSLocalizedString(@"RENAME_MEDIA_TO", @""), itemName] message:nil cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", @"") otherButtonTitles:@[NSLocalizedString(@"BUTTON_RENAME", @"")]];
  955. [alert setAlertViewStyle:UIAlertViewStylePlainTextInput];
  956. [[alert textFieldAtIndex:0] setText:itemName];
  957. [[alert textFieldAtIndex:0] setClearButtonMode:UITextFieldViewModeAlways];
  958. __weak VLCAlertView *weakAlert = alert;
  959. alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
  960. if (cancelled)
  961. [self _endEditingWithHardReset:NO];
  962. else
  963. [self renameMediaObjectTo:[weakAlert textFieldAtIndex:0].text];
  964. };
  965. [alert show];
  966. }
  967. - (void)renameMediaObjectTo:(NSString*)newName
  968. {
  969. NSArray *indexPaths;
  970. if (!_usingTableViewToShowData)
  971. indexPaths = [self.collectionView indexPathsForSelectedItems];
  972. else
  973. indexPaths = [self.tableView indexPathsForSelectedRows];
  974. if (indexPaths.count < 1)
  975. return;
  976. id mediaObject = _foundMedia[[indexPaths[0] row]];
  977. if ([mediaObject isKindOfClass:[MLAlbum class]] || [mediaObject isKindOfClass:[MLShowEpisode class]] || [mediaObject isKindOfClass:[MLShow class]] || [mediaObject isKindOfClass:[MLLabel class]] )
  978. [mediaObject setName:newName];
  979. else
  980. [mediaObject setTitle:newName];
  981. if (!_usingTableViewToShowData)
  982. [self.collectionView deselectItemAtIndexPath:indexPaths[0] animated:YES];
  983. else
  984. [self.tableView deselectRowAtIndexPath:indexPaths[0] animated:YES];
  985. if (indexPaths.count > 1)
  986. [self renameSelection];
  987. else
  988. [self _endEditingWithHardReset:NO];
  989. }
  990. #pragma mark - coin coin
  991. - (void)setLibraryMode:(VLCLibraryMode)mode
  992. {
  993. _libraryMode = mode;
  994. [self updateViewContents];
  995. }
  996. #pragma mark - autorotation
  997. // RootController is responsible for supporting interface orientation(iOS6.0+), i.e. navigation controller
  998. // so this will not work as intended without "voodoo magic"(UINavigationController category, subclassing, etc)
  999. /* introduced in iOS 6 */
  1000. - (NSUInteger)supportedInterfaceOrientations
  1001. {
  1002. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
  1003. return UIInterfaceOrientationMaskAll;
  1004. return (_foundMedia.count > 0)? UIInterfaceOrientationMaskAllButUpsideDown:
  1005. UIInterfaceOrientationMaskPortrait;
  1006. }
  1007. /* introduced in iOS 6 */
  1008. - (BOOL)shouldAutorotate
  1009. {
  1010. return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || (_foundMedia.count > 0);
  1011. }
  1012. - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
  1013. {
  1014. [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
  1015. if (!_usingTableViewToShowData)
  1016. [self.collectionView.collectionViewLayout invalidateLayout];
  1017. }
  1018. #pragma mark - Search Display Controller Delegate
  1019. - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
  1020. {
  1021. NSInteger listCount = _foundMedia.count;
  1022. [_searchData removeAllObjects];
  1023. NSManagedObject *item;
  1024. NSRange nameRange;
  1025. for (int i = 0; i < listCount; i++) {
  1026. item = _foundMedia[i];
  1027. if ([item isKindOfClass:[MLAlbum class]]) {
  1028. nameRange = [[(MLAlbum*)item name] rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1029. if (nameRange.location == NSNotFound) {
  1030. NSString *releaseYear = [(MLAlbum *)item releaseYear];
  1031. if (releaseYear)
  1032. nameRange = [releaseYear rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1033. /* user didn't search for our album name or year, let's do a deeper search */
  1034. if (nameRange.location == NSNotFound) {
  1035. NSArray *tracks = [(MLAlbum*)item sortedTracks];
  1036. NSUInteger trackCount = tracks.count;
  1037. for (NSUInteger x = 0; x < trackCount; x++) {
  1038. nameRange = [self _processItem:tracks[x] withString:searchString];
  1039. if (nameRange.location != NSNotFound)
  1040. break;
  1041. }
  1042. }
  1043. }
  1044. } else if ([item isKindOfClass:[MLShow class]]) {
  1045. nameRange = [[(MLShow*)item name] rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1046. /* user didn't search for our show name, let's do a deeper search */
  1047. if (nameRange.location == NSNotFound) {
  1048. NSArray *episodes = [(MLShow*)item sortedEpisodes];
  1049. NSUInteger episodeCount = episodes.count;
  1050. NSString *name;
  1051. for (NSUInteger x = 0; x < episodeCount; x++) {
  1052. name = [(MLShowEpisode*)episodes[x] name];
  1053. if (name)
  1054. nameRange = [name rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1055. if (nameRange.location != NSNotFound) {
  1056. break;
  1057. }
  1058. }
  1059. }
  1060. } else if ([item isKindOfClass:[MLLabel class]]) {
  1061. nameRange = [[(MLLabel*)item name] rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1062. /* user didn't search for our label name, let's do a deeper search */
  1063. if (nameRange.location == NSNotFound) {
  1064. NSArray *files = [(MLLabel*)item sortedFolderItems];
  1065. NSUInteger fileCount = files.count;
  1066. for (NSUInteger x = 0; x < fileCount; x++) {
  1067. nameRange = [self _processItem:files[x] withString:searchString];
  1068. if (nameRange.location != NSNotFound)
  1069. break;
  1070. }
  1071. }
  1072. } else // simple file
  1073. nameRange = [self _processItem:(MLFile*)item withString:searchString];
  1074. if (nameRange.location != NSNotFound)
  1075. [_searchData addObject:item];
  1076. }
  1077. return YES;
  1078. }
  1079. - (NSRange)_processItem:(NSManagedObject *)file withString:(NSString *)searchString
  1080. {
  1081. NSRange nameRange;
  1082. nameRange = [[(MLFile *)file title] rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1083. if (nameRange.location == NSNotFound) {
  1084. NSString *artist = [(MLFile *)file artist];
  1085. if (artist)
  1086. nameRange = [artist rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1087. if (nameRange.location == NSNotFound && ![file isKindOfClass:[MLAlbumTrack class]]) {
  1088. NSString *releaseYear = [(MLFile *)file releaseYear];
  1089. if (releaseYear)
  1090. nameRange = [releaseYear rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1091. }
  1092. }
  1093. return nameRange;
  1094. }
  1095. - (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView
  1096. {
  1097. tableView.rowHeight = 90.;
  1098. tableView.backgroundColor = [UIColor blackColor];
  1099. }
  1100. @end