VLCPlaylistViewController.m 66 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513
  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. #import "VLCOpenInActivity.h"
  29. #import "VLCLibraryHeaderView.h"
  30. #import <AssetsLibrary/AssetsLibrary.h>
  31. /* prefs keys */
  32. static NSString *kDisplayedFirstSteps = @"Did we display the first steps tutorial?";
  33. @implementation EmptyLibraryView
  34. - (IBAction)learnMore:(id)sender
  35. {
  36. UIViewController *firstStepsVC = [[VLCFirstStepsViewController alloc] initWithNibName:nil bundle:nil];
  37. UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:firstStepsVC];
  38. navCon.modalPresentationStyle = UIModalPresentationFormSheet;
  39. [navCon loadTheme];
  40. [self.window.rootViewController presentViewController:navCon animated:YES completion:nil];
  41. }
  42. @end
  43. @interface VLCPlaylistViewController () <VLCFolderCollectionViewDelegateFlowLayout, LXReorderableCollectionViewDataSource, LXReorderableCollectionViewDelegateFlowLayout, UITableViewDataSource, UITableViewDelegate, MLMediaLibrary, VLCMediaListDelegate, UISearchBarDelegate, UISearchDisplayDelegate> {
  44. NSMutableArray *_foundMedia;
  45. VLCLibraryMode _libraryMode;
  46. VLCLibraryMode _previousLibraryMode;
  47. UIBarButtonItem *_menuButton;
  48. NSMutableArray *_indexPaths;
  49. id _folderObject;
  50. VLCFolderCollectionViewFlowLayout *_folderLayout;
  51. LXReorderableCollectionViewFlowLayout *_reorderLayout;
  52. BOOL inFolder;
  53. UILongPressGestureRecognizer *_longPressGestureRecognizer;
  54. NSInteger _mediaToPlayIndex;
  55. VLCMediaList *_list;
  56. NSArray *_tracks;
  57. NSMutableArray *_searchData;
  58. UISearchBar *_searchBar;
  59. UISearchDisplayController *_searchDisplayController;
  60. BOOL _usingTableViewToShowData;
  61. UIBarButtonItem *_actionBarButtonItem;
  62. VLCOpenInActivity *_openInActivity;
  63. }
  64. @property (nonatomic, strong) UITableView *tableView;
  65. @property (nonatomic, strong) UICollectionView *collectionView;
  66. @property (nonatomic, strong) EmptyLibraryView *emptyLibraryView;
  67. @end
  68. @implementation VLCPlaylistViewController
  69. + (void)initialize
  70. {
  71. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  72. [defaults registerDefaults:@{kDisplayedFirstSteps : [NSNumber numberWithBool:NO]}];
  73. }
  74. - (void)loadView
  75. {
  76. _usingTableViewToShowData = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone;
  77. [self setupContentViewWithContentInset:NO];
  78. _libraryMode = VLCLibraryModeAllFiles;
  79. self.emptyLibraryView = [[[NSBundle mainBundle] loadNibNamed:@"VLCEmptyLibraryView" owner:self options:nil] lastObject];
  80. _emptyLibraryView.emptyLibraryLongDescriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
  81. _emptyLibraryView.emptyLibraryLongDescriptionLabel.numberOfLines = 0;
  82. }
  83. - (void)setupContentViewWithContentInset:(BOOL)setInset
  84. {
  85. if (_usingTableViewToShowData) {
  86. if(!_tableView) {
  87. _tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStylePlain];
  88. _tableView.backgroundColor = [UIColor VLCDarkBackgroundColor];
  89. _tableView.rowHeight = [VLCPlaylistTableViewCell heightOfCell];
  90. _tableView.separatorColor = [UIColor VLCDarkBackgroundColor];
  91. _tableView.delegate = self;
  92. _tableView.dataSource = self;
  93. _tableView.opaque = YES;
  94. _tableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
  95. }
  96. self.view = _tableView;
  97. [_tableView reloadData];
  98. } else {
  99. if (!_collectionView) {
  100. _folderLayout = [[VLCFolderCollectionViewFlowLayout alloc] init];
  101. _folderLayout.headerReferenceSize = CGSizeMake(640., [VLCLibraryHeaderView headerHeight]);
  102. _collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:_folderLayout];
  103. _collectionView.alwaysBounceVertical = YES;
  104. _collectionView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
  105. _collectionView.delegate = self;
  106. _collectionView.dataSource = self;
  107. _collectionView.opaque = YES;
  108. _collectionView.backgroundColor = [UIColor VLCDarkBackgroundColor];
  109. _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_collectionViewHandleLongPressGesture:)];
  110. [_collectionView addGestureRecognizer:_longPressGestureRecognizer];
  111. if (SYSTEM_RUNS_IOS7_OR_LATER)
  112. [_collectionView registerNib:[UINib nibWithNibName:@"VLCFuturePlaylistCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"PlaylistCell"];
  113. else
  114. [_collectionView registerNib:[UINib nibWithNibName:@"VLCPlaylistCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"PlaylistCell"];
  115. [_collectionView registerClass:[VLCLibraryHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"HeaderCell"];
  116. self.view.backgroundColor = [UIColor VLCDarkBackgroundColor];
  117. }
  118. self.view = _collectionView;
  119. [_collectionView reloadData];
  120. }
  121. if (setInset) {
  122. CGSize statusBarSize = [UIApplication sharedApplication].statusBarFrame.size;
  123. // Status bar frame doesn't change correctly on rotation
  124. CGFloat statusBarHeight = MIN(statusBarSize.height, statusBarSize.width);
  125. CGFloat originY = self.navigationController.navigationBar.frame.size.height + statusBarHeight;
  126. if (_usingTableViewToShowData)
  127. _tableView.contentInset = UIEdgeInsetsMake(originY, 0, 0, 0);
  128. else {
  129. if (_searchBar.hidden)
  130. _collectionView.contentInset = UIEdgeInsetsMake(originY - [VLCLibraryHeaderView headerHeight], 0, 0, 0);
  131. else {
  132. _collectionView.contentInset = UIEdgeInsetsMake(originY, 0, 0, 0);
  133. [_collectionView scrollRectToVisible:CGRectMake(0., 0., 640., 200.) animated:NO];
  134. }
  135. }
  136. }
  137. self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  138. }
  139. #pragma mark -
  140. - (void)viewDidLoad
  141. {
  142. [super viewDidLoad];
  143. self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", nil);
  144. _menuButton = [UIBarButtonItem themedRevealMenuButtonWithTarget:self andSelector:@selector(leftButtonAction:)];
  145. self.navigationItem.leftBarButtonItem = _menuButton;
  146. if (SYSTEM_RUNS_IOS7_OR_LATER)
  147. self.editButtonItem.tintColor = [UIColor whiteColor];
  148. else {
  149. [self.editButtonItem setBackgroundImage:[UIImage imageNamed:@"button"]
  150. forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
  151. [self.editButtonItem setBackgroundImage:[UIImage imageNamed:@"buttonHighlight"]
  152. forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
  153. }
  154. _emptyLibraryView.emptyLibraryLabel.text = NSLocalizedString(@"EMPTY_LIBRARY", nil);
  155. _emptyLibraryView.emptyLibraryLongDescriptionLabel.text = NSLocalizedString(@"EMPTY_LIBRARY_LONG", nil);
  156. [_emptyLibraryView.emptyLibraryLongDescriptionLabel sizeToFit];
  157. [_emptyLibraryView.learnMoreButton setTitle:NSLocalizedString(@"BUTTON_LEARN_MORE", nil) forState:UIControlStateNormal];
  158. UIBarButtonItem *createFolderItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemOrganize target:self action:@selector(createFolder)];
  159. // Better visual alignment with the action button
  160. UIEdgeInsets insets = UIEdgeInsetsMake(4, 0, 0, 0);
  161. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  162. insets.top += 1;
  163. }
  164. createFolderItem.imageInsets = insets;
  165. _actionBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(actOnSelection:)];
  166. _actionBarButtonItem.enabled = NO;
  167. // If you find strange issues with multiple Edit/Cancel actions causing UIToolbar spacing corruption, use a flexible space instead of a fixed space.
  168. UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
  169. fixedSpace.width = 20;
  170. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  171. fixedSpace.width *= 2;
  172. }
  173. [self setToolbarItems:@[_actionBarButtonItem, fixedSpace, createFolderItem, [[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], [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteSelection)]]];
  174. self.navigationController.toolbar.barStyle = UIBarStyleBlack;
  175. if (SYSTEM_RUNS_IOS7_OR_LATER) {
  176. self.navigationController.toolbar.tintColor = [UIColor whiteColor];
  177. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
  178. } else
  179. [self.navigationController.toolbar setBackgroundImage:[UIImage imageNamed:@"bottomBlackBar"] forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
  180. _searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
  181. UINavigationBar *navBar = self.navigationController.navigationBar;
  182. if (SYSTEM_RUNS_IOS7_OR_LATER) {
  183. _searchBar.barTintColor = navBar.barTintColor;
  184. }
  185. _searchBar.tintColor = navBar.tintColor;
  186. _searchBar.translucent = navBar.translucent;
  187. _searchBar.opaque = navBar.opaque;
  188. _searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:_searchBar contentsController:self];
  189. _searchDisplayController.delegate = self;
  190. _searchDisplayController.searchResultsDataSource = self;
  191. _searchDisplayController.searchResultsDelegate = self;
  192. _searchDisplayController.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  193. _searchDisplayController.searchResultsTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
  194. _searchBar.delegate = self;
  195. UITapGestureRecognizer *tapTwiceGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapTwiceGestureAction:)];
  196. [tapTwiceGesture setNumberOfTapsRequired:2];
  197. [self.navigationController.navigationBar addGestureRecognizer:tapTwiceGesture];
  198. _searchData = [[NSMutableArray alloc] init];
  199. }
  200. - (void)viewWillAppear:(BOOL)animated
  201. {
  202. [super viewWillAppear:animated];
  203. [self.collectionView.collectionViewLayout invalidateLayout];
  204. [self _displayEmptyLibraryViewIfNeeded];
  205. }
  206. - (void)viewDidAppear:(BOOL)animated
  207. {
  208. [super viewDidAppear:animated];
  209. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  210. if (![[defaults objectForKey:kDisplayedFirstSteps] boolValue]) {
  211. [self.emptyLibraryView performSelector:@selector(learnMore:) withObject:nil afterDelay:1.];
  212. [defaults setObject:[NSNumber numberWithBool:YES] forKey:kDisplayedFirstSteps];
  213. [defaults synchronize];
  214. }
  215. if ([[MLMediaLibrary sharedMediaLibrary] libraryNeedsUpgrade]) {
  216. self.navigationItem.rightBarButtonItem = nil;
  217. self.navigationItem.leftBarButtonItem = nil;
  218. self.title = @"";
  219. self.emptyLibraryView.emptyLibraryLabel.text = NSLocalizedString(@"UPGRADING_LIBRARY", nil);
  220. self.emptyLibraryView.emptyLibraryLongDescriptionLabel.hidden = YES;
  221. [self.emptyLibraryView.activityIndicator startAnimating];
  222. self.emptyLibraryView.frame = self.view.bounds;
  223. [self.view addSubview:self.emptyLibraryView];
  224. [[MLMediaLibrary sharedMediaLibrary] setDelegate: self];
  225. [[MLMediaLibrary sharedMediaLibrary] performSelectorInBackground:@selector(upgradeLibrary) withObject:nil];
  226. return;
  227. }
  228. if (_foundMedia.count < 1)
  229. [self updateViewContents];
  230. [[MLMediaLibrary sharedMediaLibrary] performSelector:@selector(libraryDidAppear) withObject:nil afterDelay:1.];
  231. }
  232. - (void)viewDidDisappear:(BOOL)animated
  233. {
  234. [super viewDidDisappear:animated];
  235. [[MLMediaLibrary sharedMediaLibrary] libraryDidDisappear];
  236. }
  237. - (BOOL)canBecomeFirstResponder
  238. {
  239. return YES;
  240. }
  241. - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
  242. {
  243. if (motion == UIEventSubtypeMotionShake)
  244. [[VLCBugreporter sharedInstance] handleBugreportRequest];
  245. }
  246. - (void)openMediaObject:(NSManagedObject *)mediaObject
  247. {
  248. if ([mediaObject isKindOfClass:[MLAlbum class]]) {
  249. _foundMedia = [NSMutableArray arrayWithArray:[(MLAlbum *)mediaObject sortedTracks]];
  250. self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedBackButtonWithTarget:self andSelector:@selector(backToAllItems:)];
  251. if (_libraryMode == VLCLibraryModeAllFiles)
  252. self.navigationItem.leftBarButtonItem.title = NSLocalizedString(@"BUTTON_BACK", nil);
  253. else
  254. [self.navigationItem.leftBarButtonItem setTitle:NSLocalizedString(@"LIBRARY_MUSIC", nil)];
  255. self.title = [(MLAlbum*)mediaObject name];
  256. [self reloadViews];
  257. } else if ([mediaObject isKindOfClass:[MLShow class]]) {
  258. _foundMedia = [NSMutableArray arrayWithArray:[(MLShow *)mediaObject sortedEpisodes]];
  259. self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedBackButtonWithTarget:self andSelector:@selector(backToAllItems:)];
  260. if (_libraryMode == VLCLibraryModeAllFiles)
  261. self.navigationItem.leftBarButtonItem.title = NSLocalizedString(@"BUTTON_BACK", nil);
  262. else
  263. [self.navigationItem.leftBarButtonItem setTitle:NSLocalizedString(@"LIBRARY_SERIES", nil)];
  264. self.title = [(MLShow*)mediaObject name];
  265. [self reloadViews];
  266. } else if ([mediaObject isKindOfClass:[MLLabel class]]) {
  267. MLLabel *folder = (MLLabel*) mediaObject;
  268. inFolder = YES;
  269. if (!_usingTableViewToShowData) {
  270. if (![self.collectionView.collectionViewLayout isEqual:_reorderLayout]) {
  271. for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers) {
  272. if (recognizer == _folderLayout.panGestureRecognizer || recognizer == _folderLayout.longPressGestureRecognizer || recognizer == _longPressGestureRecognizer)
  273. [self.collectionView removeGestureRecognizer:recognizer];
  274. }
  275. _reorderLayout = [[LXReorderableCollectionViewFlowLayout alloc] init];
  276. [self.collectionView setCollectionViewLayout:_reorderLayout animated:NO];
  277. }
  278. }
  279. _foundMedia = [NSMutableArray arrayWithArray:[folder sortedFolderItems]];
  280. self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedBackButtonWithTarget:self andSelector:@selector(backToAllItems:)];
  281. self.navigationItem.leftBarButtonItem.title = NSLocalizedString(@"BUTTON_BACK", nil);
  282. self.title = [folder name];
  283. UIBarButtonItem *removeFromFolder = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemReply target:self action:@selector(removeFromFolder)];
  284. NSMutableArray *toolbarItems = [self.toolbarItems mutableCopy];
  285. toolbarItems[2] = removeFromFolder;
  286. self.toolbarItems = toolbarItems;
  287. [self reloadViews];
  288. return;
  289. } else
  290. [(VLCAppDelegate*)[UIApplication sharedApplication].delegate openMediaFromManagedObject:mediaObject];
  291. }
  292. - (void)removeMediaObject:(id)managedObject updateDatabase:(BOOL)updateDB
  293. {
  294. // delete all tracks from an album
  295. if ([managedObject isKindOfClass:[MLAlbum class]]) {
  296. MLAlbum *album = managedObject;
  297. NSSet *iterAlbumTrack = [NSSet setWithSet:album.tracks];
  298. for (MLAlbumTrack *track in iterAlbumTrack) {
  299. NSSet *iterFiles = [NSSet setWithSet:track.files];
  300. for (MLFile *file in iterFiles)
  301. [self _deleteMediaObject:file];
  302. }
  303. // delete all episodes from a show
  304. } else if ([managedObject isKindOfClass:[MLShow class]]) {
  305. MLShow *show = managedObject;
  306. NSSet *iterShowEpisodes = [NSSet setWithSet:show.episodes];
  307. for (MLShowEpisode *episode in iterShowEpisodes) {
  308. NSSet *iterFiles = [NSSet setWithSet:episode.files];
  309. for (MLFile *file in iterFiles)
  310. [self _deleteMediaObject:file];
  311. }
  312. // delete all files from an episode
  313. } else if ([managedObject isKindOfClass:[MLShowEpisode class]]) {
  314. MLShowEpisode *episode = managedObject;
  315. NSSet *iterFiles = [NSSet setWithSet:episode.files];
  316. for (MLFile *file in iterFiles)
  317. [self _deleteMediaObject:file];
  318. // delete all files from a track
  319. } else if ([managedObject isKindOfClass:[MLAlbumTrack class]]) {
  320. MLAlbumTrack *track = managedObject;
  321. NSSet *iterFiles = [NSSet setWithSet:track.files];
  322. for (MLFile *file in iterFiles)
  323. [self _deleteMediaObject:file];
  324. } else if ([managedObject isKindOfClass:[MLLabel class]]) {
  325. MLLabel *folder = managedObject;
  326. NSSet *iterFiles = [NSSet setWithSet:folder.files];
  327. [folder removeFiles:folder.files];
  328. for (MLFile *file in iterFiles)
  329. [self _deleteMediaObject:file];
  330. [[MLMediaLibrary sharedMediaLibrary] removeObject:folder];
  331. }
  332. else
  333. [self _deleteMediaObject:managedObject];
  334. if (updateDB) {
  335. [[MLMediaLibrary sharedMediaLibrary] updateMediaDatabase];
  336. [self updateViewContents];
  337. }
  338. }
  339. - (void)_deleteMediaObject:(MLFile *)mediaObject
  340. {
  341. if (inFolder)
  342. [self rearrangeFolderTrackNumbersForRemovedItem:mediaObject];
  343. NSFileManager *fileManager = [NSFileManager defaultManager];
  344. NSString *folderLocation = [[[NSURL URLWithString:mediaObject.url] path] stringByDeletingLastPathComponent];
  345. NSArray *allfiles = [fileManager contentsOfDirectoryAtPath:folderLocation error:nil];
  346. NSString *fileName = [[[[NSURL URLWithString:mediaObject.url] path] lastPathComponent] stringByDeletingPathExtension];
  347. NSIndexSet *indexSet = [allfiles indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
  348. return ([obj rangeOfString:fileName].location != NSNotFound);
  349. }];
  350. NSUInteger count = indexSet.count;
  351. NSString *additionalFilePath;
  352. NSUInteger currentIndex = [indexSet firstIndex];
  353. for (unsigned int x = 0; x < count; x++) {
  354. additionalFilePath = allfiles[currentIndex];
  355. if ([additionalFilePath isSupportedSubtitleFormat])
  356. [fileManager removeItemAtPath:[folderLocation stringByAppendingPathComponent:additionalFilePath] error:nil];
  357. currentIndex = [indexSet indexGreaterThanIndex:currentIndex];
  358. }
  359. [fileManager removeItemAtPath:[[NSURL URLWithString:mediaObject.url] path] error:nil];
  360. }
  361. - (void)_displayEmptyLibraryViewIfNeeded
  362. {
  363. if (self.emptyLibraryView.superview)
  364. [self.emptyLibraryView removeFromSuperview];
  365. if (_foundMedia.count == 0) {
  366. self.emptyLibraryView.emptyLibraryLabel.text = inFolder ? NSLocalizedString(@"FOLDER_EMPTY", nil) : NSLocalizedString(@"EMPTY_LIBRARY", nil);
  367. self.emptyLibraryView.emptyLibraryLongDescriptionLabel.text = inFolder ? NSLocalizedString(@"FOLDER_EMPTY_LONG", nil) : NSLocalizedString(@"EMPTY_LIBRARY_LONG", nil);
  368. self.emptyLibraryView.learnMoreButton.hidden = inFolder;
  369. self.emptyLibraryView.frame = self.view.bounds;
  370. [self.view addSubview:self.emptyLibraryView];
  371. self.navigationItem.rightBarButtonItem = nil;
  372. } else {
  373. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  374. UIBarButtonItem *toggleDisplayedView = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"tableViewIcon"] style:UIBarButtonItemStylePlain target:self action:@selector(toggleDisplayedView:)];
  375. self.navigationItem.rightBarButtonItems = @[toggleDisplayedView, self.editButtonItem];
  376. } else {
  377. self.navigationItem.rightBarButtonItem = self.editButtonItem;
  378. }
  379. }
  380. if (_usingTableViewToShowData)
  381. _tableView.separatorStyle = (_foundMedia.count > 0)? UITableViewCellSeparatorStyleSingleLine:
  382. UITableViewCellSeparatorStyleNone;
  383. else
  384. [self.collectionView.collectionViewLayout invalidateLayout];
  385. }
  386. - (void)libraryUpgradeComplete
  387. {
  388. self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", nil);
  389. self.navigationItem.leftBarButtonItem = _menuButton;
  390. self.emptyLibraryView.emptyLibraryLongDescriptionLabel.hidden = NO;
  391. self.emptyLibraryView.emptyLibraryLabel.text = NSLocalizedString(@"EMPTY_LIBRARY", nil);
  392. [self.emptyLibraryView.activityIndicator stopAnimating];
  393. [self.emptyLibraryView removeFromSuperview];
  394. [self updateViewContents];
  395. }
  396. - (void)libraryWasUpdated
  397. {
  398. [self updateViewContents];
  399. }
  400. - (void)updateViewContents
  401. {
  402. _foundMedia = [[NSMutableArray alloc] init];
  403. self.navigationItem.leftBarButtonItem = _menuButton;
  404. if (_libraryMode == VLCLibraryModeAllAlbums)
  405. self.title = NSLocalizedString(@"LIBRARY_MUSIC", nil);
  406. else if( _libraryMode == VLCLibraryModeAllSeries)
  407. self.title = NSLocalizedString(@"LIBRARY_SERIES", nil);
  408. else
  409. self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", nil);
  410. /* add all albums */
  411. if (_libraryMode != VLCLibraryModeAllSeries) {
  412. NSArray *rawAlbums = [MLAlbum allAlbums];
  413. for (MLAlbum *album in rawAlbums) {
  414. if (album.name.length > 0 && album.tracks.count > 1)
  415. [_foundMedia addObject:album];
  416. }
  417. }
  418. if (_libraryMode == VLCLibraryModeAllAlbums) {
  419. [self reloadViews];
  420. return;
  421. }
  422. /* add all shows */
  423. NSArray *rawShows = [MLShow allShows];
  424. for (MLShow *show in rawShows) {
  425. if (show.name.length > 0 && show.episodes.count > 1)
  426. [_foundMedia addObject:show];
  427. }
  428. if (_libraryMode == VLCLibraryModeAllSeries) {
  429. [self reloadViews];
  430. return;
  431. }
  432. /* add all folders*/
  433. NSArray *allFolders = [MLLabel allLabels];
  434. for (MLLabel *folder in allFolders)
  435. [_foundMedia addObject:folder];
  436. /* add all remaining files */
  437. NSArray *allFiles = [MLFile allFiles];
  438. for (MLFile *file in allFiles) {
  439. if (file.labels.count > 0) continue;
  440. if (!file.isShowEpisode && !file.isAlbumTrack)
  441. [_foundMedia addObject:file];
  442. else if (file.isShowEpisode) {
  443. if (file.showEpisode.show.episodes.count < 2)
  444. [_foundMedia addObject:file];
  445. /* older MediaLibraryKit versions don't send a show name in a popular
  446. * corner case. hence, we need to work-around here and force a reload
  447. * afterwards as this could lead to the 'all my shows are gone'
  448. * syndrome (see #10435, #10464, #10432 et al) */
  449. if (file.showEpisode.show.name.length == 0) {
  450. file.showEpisode.show.name = NSLocalizedString(@"UNTITLED_SHOW", nil);
  451. [self performSelector:@selector(updateViewContents) withObject:nil afterDelay:0.1];
  452. }
  453. } else if (file.isAlbumTrack) {
  454. if (file.albumTrack.album.tracks.count < 2)
  455. [_foundMedia addObject:file];
  456. }
  457. }
  458. [self reloadViews];
  459. }
  460. - (void)reloadViews
  461. {
  462. // 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.
  463. if (_usingTableViewToShowData) {
  464. [self.tableView reloadData];
  465. [self updateActionBarButtonItemStateWithSelectedIndexPaths:[self.tableView indexPathsForSelectedRows]];
  466. } else {
  467. [self.collectionView reloadData];
  468. [self updateActionBarButtonItemStateWithSelectedIndexPaths:[self.collectionView indexPathsForSelectedItems]];
  469. }
  470. [self _displayEmptyLibraryViewIfNeeded];
  471. }
  472. #pragma mark - Table View
  473. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  474. {
  475. return 1;
  476. }
  477. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  478. {
  479. if (tableView == self.tableView)
  480. return _foundMedia.count;
  481. return _searchData.count;
  482. }
  483. // Customize the appearance of table view cells.
  484. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  485. {
  486. static NSString *CellIdentifier = @"PlaylistCell";
  487. VLCPlaylistTableViewCell *cell = (VLCPlaylistTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  488. if (cell == nil)
  489. cell = [VLCPlaylistTableViewCell cellWithReuseIdentifier:CellIdentifier];
  490. UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRightGestureAction:)];
  491. [swipeRight setDirection:(UISwipeGestureRecognizerDirectionRight)];
  492. [cell addGestureRecognizer:swipeRight];
  493. NSInteger row = indexPath.row;
  494. if (tableView == self.tableView)
  495. cell.mediaObject = _foundMedia[row];
  496. else
  497. cell.mediaObject = _searchData[row];
  498. return cell;
  499. }
  500. - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
  501. {
  502. MLFile* object = [_foundMedia objectAtIndex:fromIndexPath.item];
  503. [_foundMedia removeObjectAtIndex:fromIndexPath.item];
  504. [_foundMedia insertObject:object atIndex:toIndexPath.item];
  505. object.folderTrackNumber = @(toIndexPath.item - 1);
  506. object = [_foundMedia objectAtIndex:fromIndexPath.item];
  507. object.folderTrackNumber = @(fromIndexPath.item - 1);
  508. }
  509. - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
  510. {
  511. return inFolder;
  512. }
  513. - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
  514. {
  515. cell.backgroundColor = (indexPath.row % 2 == 0)? [UIColor blackColor]: [UIColor VLCDarkBackgroundColor];
  516. cell.multipleSelectionBackgroundView.backgroundColor = cell.backgroundColor;
  517. }
  518. - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
  519. {
  520. return YES;
  521. }
  522. - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
  523. {
  524. if (editingStyle == UITableViewCellEditingStyleDelete)
  525. [self removeMediaObject: _foundMedia[indexPath.row] updateDatabase:YES];
  526. }
  527. - (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
  528. {
  529. if (tableView.isEditing) {
  530. [self updateActionBarButtonItemStateWithSelectedIndexPaths:[tableView indexPathsForSelectedRows]];
  531. }
  532. }
  533. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  534. {
  535. if (tableView.isEditing) {
  536. if (_libraryMode == VLCLibraryModeCreateFolder) {
  537. _folderObject = _foundMedia[indexPath.row];
  538. _libraryMode = _previousLibraryMode;
  539. [self updateViewContents];
  540. [self createFolderWithName:nil];
  541. } else {
  542. [self updateActionBarButtonItemStateWithSelectedIndexPaths:[tableView indexPathsForSelectedRows]];
  543. }
  544. return;
  545. }
  546. [tableView deselectRowAtIndexPath:indexPath animated:YES];
  547. NSManagedObject *selectedObject;
  548. if (tableView == self.tableView)
  549. selectedObject = _foundMedia[indexPath.row];
  550. else
  551. selectedObject = _searchData[indexPath.row];
  552. if ([selectedObject isKindOfClass:[MLAlbumTrack class]]) {
  553. _tracks = [[(MLAlbumTrack*)selectedObject album] sortedTracks];
  554. NSUInteger count = _tracks.count;
  555. _list = [[VLCMediaList alloc] init];
  556. _list.delegate = self;
  557. MLFile *file;
  558. VLCMedia *media;
  559. for (NSInteger x = count - 1; x > -1; x--) {
  560. file = [(MLAlbumTrack*)_tracks[x] files].anyObject;
  561. media = [VLCMedia mediaWithURL: [NSURL URLWithString:file.url]];
  562. [media parse];
  563. [_list addMedia:media];
  564. }
  565. _mediaToPlayIndex = indexPath.row;
  566. } else if ([selectedObject isKindOfClass:[MLFile class]] && [((MLFile *)selectedObject).labels count] > 0) {
  567. MLLabel *folder = [((MLFile *)selectedObject).labels anyObject];
  568. _tracks = [folder sortedFolderItems];
  569. NSUInteger count = _tracks.count;
  570. _list = [[VLCMediaList alloc] init];
  571. _list.delegate = self;
  572. MLFile *file;
  573. for (NSInteger x = count - 1; x > -1; x--) {
  574. file = (MLFile *)_tracks[x];
  575. [_list addMedia:[VLCMedia mediaWithURL:[NSURL URLWithString:file.url]]];
  576. }
  577. _mediaToPlayIndex = indexPath.row;
  578. } else {
  579. if (_searchDisplayController.active == YES)
  580. [_searchDisplayController setActive:NO animated:NO];
  581. [self openMediaObject:selectedObject];
  582. }
  583. }
  584. #pragma mark - VLCMedialistDelegate
  585. - (void)mediaList:(VLCMediaList *)aMediaList mediaAdded:(VLCMedia *)media atIndex:(NSInteger)index
  586. {
  587. if (index == _tracks.count - 1) {
  588. NSManagedObject *selectedObject = _foundMedia[_mediaToPlayIndex];
  589. [(VLCAppDelegate *)[UIApplication sharedApplication].delegate openMediaList:_list atIndex:(int)[_tracks indexOfObject:selectedObject]];
  590. }
  591. }
  592. #pragma mark - Gesture Action
  593. - (void)swipeRightGestureAction:(UIGestureRecognizer *)recognizer
  594. {
  595. if ([[self.editButtonItem title] isEqualToString:NSLocalizedString(@"BUTTON_CANCEL", nil)])
  596. [self setEditing:NO animated:YES];
  597. else {
  598. [self setEditing:YES animated:YES];
  599. NSIndexPath *path = [(UITableView *)self.view indexPathForRowAtPoint:[recognizer locationInView:self.view]];
  600. [self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:path.row inSection:path.section]
  601. animated:YES
  602. scrollPosition:UITableViewScrollPositionNone];
  603. }
  604. }
  605. - (void)tapTwiceGestureAction:(UIGestureRecognizer *)recognizer
  606. {
  607. _searchBar.hidden = !_searchBar.hidden;
  608. if (_usingTableViewToShowData) {
  609. if (_searchBar.hidden)
  610. self.tableView.tableHeaderView = nil;
  611. else
  612. self.tableView.tableHeaderView = _searchBar;
  613. } else
  614. [self setupContentViewWithContentInset:YES];
  615. }
  616. #pragma mark - Collection View
  617. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
  618. {
  619. return _foundMedia.count;
  620. }
  621. - (UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
  622. {
  623. VLCPlaylistCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"PlaylistCell" forIndexPath:indexPath];
  624. cell.mediaObject = _foundMedia[indexPath.row];
  625. cell.collectionView = _collectionView;
  626. [cell setEditing:self.editing animated:NO];
  627. return cell;
  628. }
  629. - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
  630. {
  631. if (SYSTEM_RUNS_IOS7_OR_LATER) {
  632. if (UIDeviceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation))
  633. return CGSizeMake(341., 190.);
  634. else
  635. return CGSizeMake(384., 216.);
  636. }
  637. return CGSizeMake(298.0, 220.0);
  638. }
  639. - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
  640. {
  641. if (SYSTEM_RUNS_IOS7_OR_LATER)
  642. return UIEdgeInsetsZero;
  643. return UIEdgeInsetsMake(0.0, 34.0, 0.0, 34.0);
  644. }
  645. - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
  646. {
  647. if (SYSTEM_RUNS_IOS7_OR_LATER)
  648. return 0.;
  649. return 10.0;
  650. }
  651. - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
  652. {
  653. if (SYSTEM_RUNS_IOS7_OR_LATER)
  654. return 0.;
  655. return 10.0;
  656. }
  657. - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
  658. {
  659. if (self.editing) {
  660. if (_libraryMode == VLCLibraryModeCreateFolder) {
  661. _folderObject = _foundMedia[indexPath.item];
  662. [self createFolderWithName:nil];
  663. _libraryMode = _previousLibraryMode;
  664. } else {
  665. [self updateActionBarButtonItemStateWithSelectedIndexPaths:[collectionView indexPathsForSelectedItems]];
  666. }
  667. [(VLCPlaylistCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath] selectionUpdate];
  668. return;
  669. }
  670. NSManagedObject *selectedObject = _foundMedia[indexPath.row];
  671. if ([selectedObject isKindOfClass:[MLAlbumTrack class]]) {
  672. VLCMediaList *list;
  673. NSArray *tracks = [[(MLAlbumTrack*)selectedObject album] sortedTracks];
  674. NSUInteger count = tracks.count;
  675. list = [[VLCMediaList alloc] init];
  676. MLFile *file;
  677. for (NSInteger x = count - 1; x > -1; x--) {
  678. file = [(MLAlbumTrack*)tracks[x] files].anyObject;
  679. [list addMedia:[VLCMedia mediaWithURL: [NSURL URLWithString:file.url]]];
  680. }
  681. [(VLCAppDelegate*)[UIApplication sharedApplication].delegate openMediaList:list atIndex:(int)[tracks indexOfObject:selectedObject]];
  682. } else if ([selectedObject isKindOfClass:[MLFile class]] && [((MLFile *)selectedObject).labels count] > 0) {
  683. VLCMediaList *list;
  684. MLLabel *folder = [((MLFile *)selectedObject).labels anyObject];
  685. NSArray *folderTracks = [folder sortedFolderItems];
  686. NSUInteger count = folderTracks.count;
  687. list = [[VLCMediaList alloc] init];
  688. MLFile *file;
  689. for (NSInteger x = count - 1; x > -1; x--) {
  690. file = (MLFile *)folderTracks[x];
  691. [list addMedia:[VLCMedia mediaWithURL:[NSURL URLWithString:file.url]]];
  692. }
  693. [(VLCAppDelegate *)[UIApplication sharedApplication].delegate openMediaList:list atIndex:(int)[folderTracks indexOfObject:selectedObject]];
  694. } else
  695. [self openMediaObject:selectedObject];
  696. }
  697. - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
  698. {
  699. if (self.editing) {
  700. [self updateActionBarButtonItemStateWithSelectedIndexPaths:[collectionView indexPathsForSelectedItems]];
  701. }
  702. [(VLCPlaylistCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath] selectionUpdate];
  703. }
  704. - (void)collectionView:(UICollectionView *)collectionView removeItemFromFolderAtIndexPathIfNeeded:(NSIndexPath *)indexPath
  705. {
  706. MLFile *mediaObject = (MLFile *)_foundMedia[indexPath.item];
  707. [self rearrangeFolderTrackNumbersForRemovedItem:mediaObject];
  708. mediaObject.labels = nil;
  709. mediaObject.folderTrackNumber = nil;
  710. [self backToAllItems:nil];
  711. }
  712. - (void)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath willMoveToIndexPath:(NSIndexPath *)toIndexPath
  713. {
  714. MLFile* object = [_foundMedia objectAtIndex:fromIndexPath.item];
  715. [_foundMedia removeObjectAtIndex:fromIndexPath.item];
  716. [_foundMedia insertObject:object atIndex:toIndexPath.item];
  717. object.folderTrackNumber = @(toIndexPath.item - 1);
  718. object = [_foundMedia objectAtIndex:fromIndexPath.item];
  719. object.folderTrackNumber = @(fromIndexPath.item - 1);
  720. }
  721. - (void)collectionView:(UICollectionView *)collectionView requestToMoveItemAtIndexPath:(NSIndexPath *)itemPath intoFolderAtIndexPath:(NSIndexPath *)folderPath
  722. {
  723. BOOL validFileTypeAtFolderPath = ([_foundMedia[folderPath.item] isKindOfClass:[MLFile class]] || [_foundMedia[folderPath.item] isKindOfClass:[MLLabel class]]) && [_foundMedia[itemPath.item] isKindOfClass:[MLFile class]];
  724. if (!validFileTypeAtFolderPath) {
  725. 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)]];
  726. alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
  727. [self updateViewContents];
  728. };
  729. [alert show];
  730. return;
  731. }
  732. BOOL isFolder = [_foundMedia[folderPath.item] isKindOfClass:[MLLabel class]];
  733. if (isFolder){
  734. MLLabel *folder = _foundMedia[folderPath.item];
  735. MLFile *file = _foundMedia[itemPath.item];
  736. [file setLabels:[[NSSet alloc] initWithObjects:folder, nil]];
  737. file.folderTrackNumber = @([folder.files count] - 1);
  738. [_foundMedia removeObjectAtIndex:itemPath.item];
  739. [self updateViewContents];
  740. } else {
  741. _folderObject = _foundMedia[folderPath.item];
  742. _indexPaths = [NSMutableArray arrayWithArray:@[itemPath]];
  743. [self showCreateFolderAlert];
  744. }
  745. }
  746. - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
  747. {
  748. VLCLibraryHeaderView *reuseableView;
  749. reuseableView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"HeaderCell" forIndexPath:indexPath];
  750. if (!reuseableView)
  751. reuseableView = [[VLCLibraryHeaderView alloc] initWithPredefinedFrame];
  752. reuseableView.searchBar = _searchBar;
  753. return reuseableView;
  754. }
  755. #pragma mark - Folder implementation
  756. - (void)rearrangeFolderTrackNumbersForRemovedItem:(MLFile *) mediaObject
  757. {
  758. MLLabel *label = [mediaObject.labels anyObject];
  759. NSSet *allFiles = label.files;
  760. for (MLFile *file in allFiles) {
  761. if (file.folderTrackNumber > mediaObject.folderTrackNumber) {
  762. int value = [file.folderTrackNumber intValue];
  763. file.folderTrackNumber = [NSNumber numberWithInt:value - 1];
  764. }
  765. }
  766. }
  767. - (void)showCreateFolderAlert
  768. {
  769. 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)]];
  770. [alert setAlertViewStyle:UIAlertViewStylePlainTextInput];
  771. [[alert textFieldAtIndex:0] setText:NSLocalizedString(@"FOLDER_NAME_PLACEHOLDER", nil)];
  772. [[alert textFieldAtIndex:0] setClearButtonMode:UITextFieldViewModeAlways];
  773. __weak VLCAlertView *weakAlert = alert;
  774. alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
  775. if (cancelled)
  776. [self updateViewContents];
  777. else
  778. [self createFolderWithName:[weakAlert textFieldAtIndex:0].text];
  779. };
  780. [alert show];
  781. }
  782. - (void)createFolder
  783. {
  784. if (_libraryMode == VLCLibraryModeCreateFolder) {
  785. _libraryMode = _previousLibraryMode;
  786. [self updateViewContents];
  787. [self showCreateFolderAlert];
  788. return;
  789. }
  790. if (!_usingTableViewToShowData)
  791. _indexPaths = [NSMutableArray arrayWithArray:[self.collectionView indexPathsForSelectedItems]];
  792. else
  793. _indexPaths = [NSMutableArray arrayWithArray:[self.tableView indexPathsForSelectedRows]];
  794. for (NSInteger i = _indexPaths.count - 1; i >=0; i--) {
  795. NSIndexPath *path = _indexPaths[i];
  796. id mediaObject;
  797. if (!_usingTableViewToShowData)
  798. mediaObject = _foundMedia[path.item];
  799. else
  800. mediaObject = _foundMedia[path.row];
  801. if ([mediaObject isKindOfClass:[MLLabel class]])
  802. [_indexPaths removeObject:path];
  803. }
  804. if ([_indexPaths count] != 0) {
  805. NSArray *folder = [MLLabel allLabels];
  806. //if we already have folders display them
  807. if ([folder count] > 0) {
  808. _foundMedia = [NSMutableArray arrayWithArray:folder];
  809. self.title = NSLocalizedString(@"SELECT_FOLDER", nil);
  810. _previousLibraryMode = _libraryMode;
  811. _libraryMode = VLCLibraryModeCreateFolder;
  812. [self reloadViews];
  813. return;
  814. }
  815. }
  816. //no selected items or no existing folder ask for foldername
  817. [self showCreateFolderAlert];
  818. }
  819. - (void)removeFromFolder
  820. {
  821. if (!_usingTableViewToShowData)
  822. _indexPaths = [NSMutableArray arrayWithArray:[self.collectionView indexPathsForSelectedItems]];
  823. else
  824. _indexPaths = [NSMutableArray arrayWithArray:[self.tableView indexPathsForSelectedRows]];
  825. [_indexPaths sortUsingSelector:@selector(compare:)];
  826. for (NSInteger i = [_indexPaths count] - 1; i >= 0; i--) {
  827. NSIndexPath *path = _indexPaths[i];
  828. MLFile *file = (MLFile *)_foundMedia[_usingTableViewToShowData ? path.row : path.item];
  829. MLLabel *folder = [file.labels anyObject];
  830. [self rearrangeFolderTrackNumbersForRemovedItem:file];
  831. file.labels = nil;
  832. file.folderTrackNumber = nil;
  833. [_foundMedia removeObject:file];
  834. if ([folder.files count] == 0) {
  835. [self removeMediaObject:folder updateDatabase:YES];
  836. [self setEditing:NO];
  837. [self backToAllItems:nil];
  838. }
  839. }
  840. [self reloadViews];
  841. }
  842. - (void)createFolderWithName:(NSString *)folderName
  843. {
  844. NSArray *labels = [MLLabel allLabels];
  845. for (MLLabel *label in labels){
  846. if ([label.name isEqualToString:folderName]) {
  847. _folderObject = nil;
  848. _indexPaths = nil;
  849. 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)]];
  850. alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
  851. [self updateViewContents];
  852. };
  853. [alert show];
  854. return;
  855. }
  856. }
  857. if (_folderObject != nil) {
  858. NSUInteger folderIndex = [_foundMedia indexOfObject:_folderObject];
  859. //item got dragged onto item
  860. if ([_foundMedia[folderIndex] isKindOfClass:[MLFile class]]) {
  861. MLFile *file = _foundMedia[folderIndex];
  862. MLLabel *label = [[MLMediaLibrary sharedMediaLibrary] createObjectForEntity:@"Label"];
  863. label.name = folderName;
  864. file.labels = [NSSet setWithObjects:label,nil];
  865. NSNumber *folderTrackNumber = [NSNumber numberWithInt:(int)[label files].count - 1];
  866. file.folderTrackNumber = folderTrackNumber;
  867. [_foundMedia removeObjectAtIndex:folderIndex];
  868. [_foundMedia insertObject:label atIndex:folderIndex];
  869. MLFile *itemFile = _foundMedia[((NSIndexPath *)_indexPaths[0]).item];
  870. itemFile.labels = file.labels;
  871. [_foundMedia removeObjectAtIndex:((NSIndexPath *)_indexPaths[0]).item];
  872. itemFile.folderTrackNumber = @([label files].count - 1);
  873. } else {
  874. //item got dragged onto folder or items should be added to folder
  875. MLLabel *label = _foundMedia[folderIndex];
  876. [_indexPaths sortUsingSelector:@selector(compare:)];
  877. for (NSInteger i = [_indexPaths count] - 1; i >= 0; i--) {
  878. NSIndexPath *path = _indexPaths[i];
  879. if (_libraryMode != VLCLibraryModeCreateFolder && ![_foundMedia[path.row] isKindOfClass:[MLFile class]])
  880. continue;
  881. if (_libraryMode == VLCLibraryModeCreateFolder)
  882. [self updateViewContents];
  883. MLFile *file = _foundMedia[path.row];
  884. file.labels = [NSSet setWithObjects:label, nil];
  885. [_foundMedia removeObjectAtIndex:path.row];
  886. file.folderTrackNumber = @([label files].count - 1);
  887. }
  888. }
  889. _folderObject = nil;
  890. } else {
  891. //create new folder
  892. MLLabel *label = [[MLMediaLibrary sharedMediaLibrary] createObjectForEntity:@"Label"];
  893. label.name = folderName;
  894. //if items were selected
  895. if ([_indexPaths count] != 0) {
  896. [_indexPaths sortUsingSelector:@selector(compare:)];
  897. for (NSInteger i = [_indexPaths count] - 1; i >= 0; i--) {
  898. NSIndexPath *path = _indexPaths[i];
  899. if (!_usingTableViewToShowData) {
  900. MLFile *file = _foundMedia[path.item];
  901. file.labels = [NSSet setWithObjects:label, nil];
  902. file.folderTrackNumber = @([label files].count - 1);
  903. [_foundMedia removeObjectAtIndex:path.item];
  904. } else {
  905. MLFile *file = _foundMedia[path.row];
  906. file.labels = [NSSet setWithObjects:label, nil];
  907. file.folderTrackNumber = @([label files].count - 1);
  908. [_foundMedia removeObjectAtIndex:path.row];
  909. }
  910. }
  911. }
  912. }
  913. _indexPaths = nil;
  914. [self setEditing:NO];
  915. [self updateViewContents];
  916. }
  917. - (void)_collectionViewHandleLongPressGesture:(UIGestureRecognizer *) sender
  918. {
  919. [self setEditing:YES animated:YES];
  920. }
  921. #pragma mark - UI implementation
  922. - (void)setEditing:(BOOL)editing animated:(BOOL)animated
  923. {
  924. [super setEditing:editing animated:animated];
  925. UIBarButtonItem *editButton = self.editButtonItem;
  926. NSString *editImage = editing? @"doneButton": @"button";
  927. NSString *editImageHighlight = editing? @"doneButtonHighlight": @"buttonHighlight";
  928. if (SYSTEM_RUNS_IOS7_OR_LATER)
  929. editButton.tintColor = [UIColor whiteColor];
  930. else {
  931. [editButton setBackgroundImage:[UIImage imageNamed:editImage] forState:UIControlStateNormal
  932. barMetrics:UIBarMetricsDefault];
  933. [editButton setBackgroundImage:[UIImage imageNamed:editImageHighlight]
  934. forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
  935. [editButton setTitleTextAttributes: editing ? @{UITextAttributeTextShadowColor : [UIColor whiteColor], UITextAttributeTextColor : [UIColor blackColor]} : @{UITextAttributeTextShadowColor : [UIColor VLCDarkTextShadowColor], UITextAttributeTextColor : [UIColor whiteColor]} forState:UIControlStateNormal];
  936. }
  937. if (!_usingTableViewToShowData) {
  938. NSArray *visibleCells = self.collectionView.visibleCells;
  939. [visibleCells enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  940. VLCPlaylistCollectionViewCell *aCell = (VLCPlaylistCollectionViewCell*)obj;
  941. [aCell setEditing:editing animated:animated];
  942. }];
  943. self.collectionView.allowsMultipleSelection = editing;
  944. /* UIKit doesn't clear the selection automagically if we leave the editing mode
  945. * so we need to do so manually */
  946. if (!editing) {
  947. [self.collectionView addGestureRecognizer:_longPressGestureRecognizer];
  948. NSArray *selectedItems = [self.collectionView indexPathsForSelectedItems];
  949. NSUInteger count = selectedItems.count;
  950. for (NSUInteger x = 0; x < count; x++)
  951. [self.collectionView deselectItemAtIndexPath:selectedItems[x] animated:NO];
  952. } else
  953. [self.collectionView removeGestureRecognizer:_longPressGestureRecognizer];
  954. } else {
  955. self.tableView.allowsMultipleSelectionDuringEditing = editing;
  956. [self.tableView setEditing:editing animated:YES];
  957. [self.editButtonItem setTitle:editing ? NSLocalizedString(@"BUTTON_CANCEL", nil) : NSLocalizedString(@"BUTTON_EDIT", nil)];
  958. }
  959. if (_libraryMode == VLCLibraryModeCreateFolder) {
  960. _libraryMode = _previousLibraryMode;
  961. _indexPaths = nil;
  962. [self updateViewContents];
  963. }
  964. self.navigationController.toolbarHidden = !editing;
  965. }
  966. - (void)toggleDisplayedView:(UIBarButtonItem *)button
  967. {
  968. _usingTableViewToShowData = !_usingTableViewToShowData;
  969. UIImage *newButtonImage = [UIImage imageNamed: _usingTableViewToShowData ? @"collectionViewIcon" : @"tableViewIcon"];
  970. [button setImage:newButtonImage];
  971. [self setupContentViewWithContentInset:YES];
  972. }
  973. - (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
  974. {
  975. return UITableViewCellEditingStyleDelete;
  976. }
  977. - (IBAction)leftButtonAction:(id)sender
  978. {
  979. [[(VLCAppDelegate*)[UIApplication sharedApplication].delegate revealController] toggleSidebar:![(VLCAppDelegate*)[UIApplication sharedApplication].delegate revealController].sidebarShowing duration:kGHRevealSidebarDefaultAnimationDuration];
  980. if (self.isEditing)
  981. [self setEditing:NO animated:YES];
  982. }
  983. - (IBAction)backToAllItems:(id)sender
  984. {
  985. if (!_usingTableViewToShowData) {
  986. if (![self.collectionView.collectionViewLayout isEqual:_folderLayout]) {
  987. //for some reason the Gesturerecognizer block themselves if not removed manually
  988. for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers) {
  989. if (recognizer == _reorderLayout.panGestureRecognizer || recognizer == _reorderLayout.longPressGestureRecognizer)
  990. [self.collectionView removeGestureRecognizer:recognizer];
  991. }
  992. _folderLayout = [[VLCFolderCollectionViewFlowLayout alloc] init];
  993. _folderLayout.headerReferenceSize = CGSizeMake(640., [VLCLibraryHeaderView headerHeight]);
  994. [self.collectionView setCollectionViewLayout:_folderLayout animated:NO];
  995. [_collectionView addGestureRecognizer:_longPressGestureRecognizer];
  996. }
  997. }
  998. inFolder = NO;
  999. UIBarButtonItem *createFolderItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemOrganize target:self action:@selector(createFolder)];
  1000. NSMutableArray *toolbarItems = [self.toolbarItems mutableCopy];
  1001. toolbarItems[2] = createFolderItem;
  1002. self.toolbarItems = toolbarItems;
  1003. [self setLibraryMode:_libraryMode];
  1004. [self updateViewContents];
  1005. }
  1006. - (void)_endEditingWithHardReset:(BOOL)hardReset
  1007. {
  1008. [[MLMediaLibrary sharedMediaLibrary] updateMediaDatabase];
  1009. if (hardReset)
  1010. [self updateViewContents];
  1011. else
  1012. [self reloadViews];
  1013. [self setEditing:NO animated:YES];
  1014. }
  1015. - (void)deleteSelection
  1016. {
  1017. NSArray *indexPaths;
  1018. if (!_usingTableViewToShowData)
  1019. indexPaths = [self.collectionView indexPathsForSelectedItems];
  1020. else
  1021. indexPaths = [self.tableView indexPathsForSelectedRows];
  1022. NSUInteger count = indexPaths.count;
  1023. NSMutableArray *objects = [[NSMutableArray alloc] initWithCapacity:count];
  1024. for (NSUInteger x = 0; x < count; x++)
  1025. [objects addObject:_foundMedia[[indexPaths[x] row]]];
  1026. for (NSUInteger x = 0; x < count; x++)
  1027. [self removeMediaObject:objects[x] updateDatabase:NO];
  1028. [self _endEditingWithHardReset:YES];
  1029. }
  1030. - (void)renameSelection
  1031. {
  1032. NSArray *indexPaths;
  1033. if (!_usingTableViewToShowData)
  1034. indexPaths = [self.collectionView indexPathsForSelectedItems];
  1035. else
  1036. indexPaths = [self.tableView indexPathsForSelectedRows];
  1037. if (indexPaths.count < 1) {
  1038. [self _endEditingWithHardReset:NO];
  1039. return;
  1040. }
  1041. NSString *itemName;
  1042. if (!_usingTableViewToShowData)
  1043. itemName = [(VLCPlaylistCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPaths[0]] titleLabel].text;
  1044. else
  1045. itemName = [(VLCPlaylistTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPaths[0]] titleLabel].text;
  1046. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:[NSString stringWithFormat:NSLocalizedString(@"RENAME_MEDIA_TO", nil), itemName] message:nil cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", nil) otherButtonTitles:@[NSLocalizedString(@"BUTTON_RENAME", nil)]];
  1047. [alert setAlertViewStyle:UIAlertViewStylePlainTextInput];
  1048. [[alert textFieldAtIndex:0] setText:itemName];
  1049. [[alert textFieldAtIndex:0] setClearButtonMode:UITextFieldViewModeAlways];
  1050. __weak VLCAlertView *weakAlert = alert;
  1051. alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
  1052. if (cancelled)
  1053. [self _endEditingWithHardReset:NO];
  1054. else
  1055. [self renameMediaObjectTo:[weakAlert textFieldAtIndex:0].text];
  1056. };
  1057. [alert show];
  1058. }
  1059. - (void)renameMediaObjectTo:(NSString*)newName
  1060. {
  1061. NSArray *indexPaths;
  1062. if (!_usingTableViewToShowData)
  1063. indexPaths = [self.collectionView indexPathsForSelectedItems];
  1064. else
  1065. indexPaths = [self.tableView indexPathsForSelectedRows];
  1066. if (indexPaths.count < 1)
  1067. return;
  1068. id mediaObject = _foundMedia[[indexPaths[0] row]];
  1069. if ([mediaObject isKindOfClass:[MLAlbum class]] || [mediaObject isKindOfClass:[MLShowEpisode class]] || [mediaObject isKindOfClass:[MLShow class]] || [mediaObject isKindOfClass:[MLLabel class]] )
  1070. [mediaObject setName:newName];
  1071. else
  1072. [mediaObject setTitle:newName];
  1073. if (!_usingTableViewToShowData)
  1074. [self.collectionView deselectItemAtIndexPath:indexPaths[0] animated:YES];
  1075. else
  1076. [self.tableView deselectRowAtIndexPath:indexPaths[0] animated:YES];
  1077. if (indexPaths.count > 1)
  1078. [self renameSelection];
  1079. else
  1080. [self _endEditingWithHardReset:NO];
  1081. }
  1082. #pragma mark - Sharing
  1083. // We take the array of index paths (instead of a count) to actually examine if the content is shareable. Selecting a single folder should not enable the share button.
  1084. - (void)updateActionBarButtonItemStateWithSelectedIndexPaths:(NSArray *)indexPaths
  1085. {
  1086. NSUInteger count = [indexPaths count];
  1087. if (!indexPaths || count == 0) {
  1088. _actionBarButtonItem.enabled = NO;
  1089. } else {
  1090. // Look for at least one MLFile
  1091. for (NSUInteger x = 0; x < count; x++) {
  1092. MLFile *file = _foundMedia[[indexPaths[x] row]];
  1093. if ([file isKindOfClass:[MLFile class]]) {
  1094. _actionBarButtonItem.enabled = YES;
  1095. return;
  1096. }
  1097. }
  1098. }
  1099. }
  1100. - (void)actOnSelection:(UIBarButtonItem *)barButtonItem
  1101. {
  1102. NSParameterAssert(barButtonItem);
  1103. if (!barButtonItem) {
  1104. APLog(@"Missing a UIBarButtonItem to present from");
  1105. return;
  1106. }
  1107. NSArray *indexPaths;
  1108. if (!_usingTableViewToShowData)
  1109. indexPaths = [self.collectionView indexPathsForSelectedItems];
  1110. else
  1111. indexPaths = [self.tableView indexPathsForSelectedRows];
  1112. NSUInteger count = indexPaths.count;
  1113. if (count) {
  1114. NSMutableArray /* NSURL */ *fileURLobjects = [[NSMutableArray alloc] initWithCapacity:count];
  1115. for (NSUInteger x = 0; x < count; x++) {
  1116. MLFile *file = _foundMedia[[indexPaths[x] row]];
  1117. if ([file isKindOfClass:[MLFile class]]) {
  1118. NSURL *fileURL = [NSURL URLWithString:[file url]];
  1119. if ([fileURL isFileURL]) {
  1120. [fileURLobjects addObject:fileURL];
  1121. }
  1122. }
  1123. }
  1124. if ([fileURLobjects count]) {
  1125. // Provide some basic user feedback as UIActivityController lags in presentation sometimes (blocking the main thread).
  1126. // iOS 6 has trouble re-enabling all the icons, so only disable the sharing one. Usage of the toolbar will be disabled by UIApplication in this case anyhow.
  1127. if (SYSTEM_RUNS_IOS7_OR_LATER) {
  1128. [self.navigationController.toolbar.items makeObjectsPerformSelector:@selector(setEnabled:) withObject:@(NO)];
  1129. } else {
  1130. _actionBarButtonItem.enabled = YES;
  1131. }
  1132. // Just in case, since we are facing a possible delay from code we do not control (UIActivityViewController), disable any possible changes to selection (or exit from this screen)
  1133. [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
  1134. // The reason we do a dispatch_async to the main queue here even though we are already on the main queue is because UIActivityViewController blocks the main thread at some point (either during creation or presentation), which won't let UIKit draw our call to disable the toolbar items in time. On an actual device (where the lag can be seen when UIActivityViewController is presented for the first time during an applications lifetime) this makes for a much better user experience. If you have more items to share the lag may be greater.
  1135. dispatch_async(dispatch_get_main_queue(), ^{
  1136. _openInActivity = [[VLCOpenInActivity alloc] init];
  1137. _openInActivity.presentingViewController = self;
  1138. _openInActivity.presentingBarButtonItem = barButtonItem;
  1139. dispatch_block_t enableInteractionBlock = ^{
  1140. if (SYSTEM_RUNS_IOS7_OR_LATER) {
  1141. // Strangely makeObjectsPerformSelector:withObject has trouble here (when called from presentViewController:animated:completion:)
  1142. // [self.navigationController.toolbar.items makeObjectsPerformSelector:@selector(setEnabled:) withObject:@(YES)];
  1143. for (UIBarButtonItem *item in self.navigationController.toolbar.items) {
  1144. item.enabled = YES;
  1145. }
  1146. } else {
  1147. _actionBarButtonItem.enabled = YES;
  1148. }
  1149. if ([[UIApplication sharedApplication] isIgnoringInteractionEvents]) {
  1150. [[UIApplication sharedApplication] endIgnoringInteractionEvents];
  1151. }
  1152. };
  1153. UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:fileURLobjects applicationActivities:@[_openInActivity]];
  1154. controller.completionHandler = ^(NSString *activityType, BOOL completed) {
  1155. APLog(@"UIActivityViewController finished with activity type: %@, completed: %i", activityType, completed);
  1156. // Provide some feedback if saving media to the Camera Roll. Note that this could cause a false positive if the user chooses "Don't Allow" in the permissions dialog, and UIActivityViewController does not inform us of that, so check the authorization status.
  1157. // By the time this is called, the user has not had time to choose whether to allow access to the Photos library, so only display the message if we are truly sure we got authorization. The first time the user saves to the camera roll he won't see the confirmation because of this timing issue. This is better than showing a success message when the user had denied access. A timing workaround could be developed if needed through UIApplicationDidBecomeActiveNotification (to know when the security alert view was dismissed) or through other ALAssets APIs.
  1158. if (completed && [activityType isEqualToString:UIActivityTypeSaveToCameraRoll] && [ALAssetsLibrary authorizationStatus] == ALAuthorizationStatusAuthorized) {
  1159. UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"SHARING_SUCCESS_CAMERA_ROLL", nil)
  1160. message:nil
  1161. delegate:nil
  1162. cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
  1163. otherButtonTitles:nil];
  1164. [alertView show];
  1165. }
  1166. _openInActivity = nil;
  1167. // Just in case, we could call enableInteractionBlock here. Since we are disabling touch interaction for the entire UI, to be safe that we return to the proper state: re-enable everything (if presentViewController:animated:completion: failed for some reason). But UIApplication gives us a warning even if we check isIgnoringInteractionEvents, so do not call it for now.
  1168. // enableInteractionBlock();
  1169. };
  1170. [self.navigationController presentViewController:controller animated:YES completion:enableInteractionBlock];
  1171. });
  1172. return;
  1173. }
  1174. }
  1175. UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"SHARING_ERROR_NO_FILES", nil)
  1176. message:nil
  1177. delegate:nil
  1178. cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
  1179. otherButtonTitles:nil];
  1180. [alertView show];
  1181. }
  1182. #pragma mark - coin coin
  1183. - (void)setLibraryMode:(VLCLibraryMode)mode
  1184. {
  1185. _libraryMode = mode;
  1186. [self updateViewContents];
  1187. }
  1188. #pragma mark - autorotation
  1189. // RootController is responsible for supporting interface orientation(iOS6.0+), i.e. navigation controller
  1190. // so this will not work as intended without "voodoo magic"(UINavigationController category, subclassing, etc)
  1191. /* introduced in iOS 6 */
  1192. - (NSUInteger)supportedInterfaceOrientations
  1193. {
  1194. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
  1195. return UIInterfaceOrientationMaskAll;
  1196. return (_foundMedia.count > 0)? UIInterfaceOrientationMaskAllButUpsideDown:
  1197. UIInterfaceOrientationMaskPortrait;
  1198. }
  1199. /* introduced in iOS 6 */
  1200. - (BOOL)shouldAutorotate
  1201. {
  1202. return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || (_foundMedia.count > 0);
  1203. }
  1204. - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
  1205. {
  1206. [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
  1207. if (!_usingTableViewToShowData)
  1208. [self.collectionView.collectionViewLayout invalidateLayout];
  1209. }
  1210. #pragma mark - Search Display Controller Delegate
  1211. - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
  1212. {
  1213. NSInteger listCount = _foundMedia.count;
  1214. [_searchData removeAllObjects];
  1215. NSManagedObject *item;
  1216. NSRange nameRange;
  1217. for (int i = 0; i < listCount; i++) {
  1218. item = _foundMedia[i];
  1219. if ([item isKindOfClass:[MLAlbum class]]) {
  1220. nameRange = [[(MLAlbum*)item name] rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1221. if (nameRange.location == NSNotFound) {
  1222. NSString *releaseYear = [(MLAlbum *)item releaseYear];
  1223. if (releaseYear)
  1224. nameRange = [releaseYear rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1225. /* user didn't search for our album name or year, let's do a deeper search */
  1226. if (nameRange.location == NSNotFound) {
  1227. NSArray *tracks = [(MLAlbum*)item sortedTracks];
  1228. NSUInteger trackCount = tracks.count;
  1229. for (NSUInteger x = 0; x < trackCount; x++) {
  1230. nameRange = [self _processItem:tracks[x] withString:searchString];
  1231. if (nameRange.location != NSNotFound)
  1232. break;
  1233. }
  1234. }
  1235. }
  1236. } else if ([item isKindOfClass:[MLShow class]]) {
  1237. nameRange = [[(MLShow*)item name] rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1238. /* user didn't search for our show name, let's do a deeper search */
  1239. if (nameRange.location == NSNotFound) {
  1240. NSArray *episodes = [(MLShow*)item sortedEpisodes];
  1241. NSUInteger episodeCount = episodes.count;
  1242. NSString *name;
  1243. for (NSUInteger x = 0; x < episodeCount; x++) {
  1244. name = [(MLShowEpisode*)episodes[x] name];
  1245. if (name)
  1246. nameRange = [name rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1247. if (nameRange.location != NSNotFound) {
  1248. break;
  1249. }
  1250. }
  1251. }
  1252. } else if ([item isKindOfClass:[MLLabel class]]) {
  1253. nameRange = [[(MLLabel*)item name] rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1254. /* user didn't search for our label name, let's do a deeper search */
  1255. if (nameRange.location == NSNotFound) {
  1256. NSArray *files = [(MLLabel*)item sortedFolderItems];
  1257. NSUInteger fileCount = files.count;
  1258. for (NSUInteger x = 0; x < fileCount; x++) {
  1259. nameRange = [self _processItem:files[x] withString:searchString];
  1260. if (nameRange.location != NSNotFound)
  1261. break;
  1262. }
  1263. }
  1264. } else // simple file
  1265. nameRange = [self _processItem:(MLFile*)item withString:searchString];
  1266. if (nameRange.location != NSNotFound)
  1267. [_searchData addObject:item];
  1268. }
  1269. return YES;
  1270. }
  1271. - (NSRange)_processItem:(NSManagedObject *)file withString:(NSString *)searchString
  1272. {
  1273. NSRange nameRange;
  1274. nameRange = [[(MLFile *)file title] rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1275. if (nameRange.location == NSNotFound) {
  1276. NSString *artist = [(MLFile *)file artist];
  1277. if (artist)
  1278. nameRange = [artist rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1279. if (nameRange.location == NSNotFound && ![file isKindOfClass:[MLAlbumTrack class]]) {
  1280. NSString *releaseYear = [(MLFile *)file releaseYear];
  1281. if (releaseYear)
  1282. nameRange = [releaseYear rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1283. }
  1284. }
  1285. return nameRange;
  1286. }
  1287. - (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView
  1288. {
  1289. tableView.rowHeight = 90.;
  1290. tableView.backgroundColor = [UIColor blackColor];
  1291. }
  1292. @end