VLCLibraryViewController.m 81 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953
  1. /*****************************************************************************
  2. * VLCLibraryViewController.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2013-2016 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. * Tobias Conradi <videolan # tobias-conradi.de>
  13. *
  14. * Refer to the COPYING file of the official project for license.
  15. *****************************************************************************/
  16. #import "VLCLibraryViewController.h"
  17. #import "VLCMovieViewController.h"
  18. #import "VLCPlaylistTableViewCell.h"
  19. #import "VLCPlaylistCollectionViewCell.h"
  20. #import "NSString+SupportedMedia.h"
  21. #import "VLCBugreporter.h"
  22. #import "VLCAppDelegate.h"
  23. #import "VLCFirstStepsViewController.h"
  24. #import "VLCFolderCollectionViewFlowLayout.h"
  25. #import "LXReorderableCollectionViewFlowLayout.h"
  26. #import "VLCOpenInActivity.h"
  27. #import "VLCNavigationController.h"
  28. #import "VLCPlaybackController+MediaLibrary.h"
  29. #import "VLCKeychainCoordinator.h"
  30. #import "GTScrollNavigationBar.h"
  31. #import <AssetsLibrary/AssetsLibrary.h>
  32. #import <CoreSpotlight/CoreSpotlight.h>
  33. /* prefs keys */
  34. static NSString *kDisplayedFirstSteps = @"Did we display the first steps tutorial?";
  35. static NSString *kUsingTableViewToShowData = @"UsingTableViewToShowData";
  36. @implementation EmptyLibraryView
  37. - (IBAction)learnMore:(id)sender
  38. {
  39. UIViewController *firstStepsVC = [[VLCFirstStepsViewController alloc] initWithNibName:nil bundle:nil];
  40. UINavigationController *navCon = [[VLCNavigationController alloc] initWithRootViewController:firstStepsVC];
  41. navCon.modalPresentationStyle = UIModalPresentationFormSheet;
  42. [self.window.rootViewController presentViewController:navCon animated:YES completion:nil];
  43. }
  44. @end
  45. @interface VLCLibraryViewController () <VLCFolderCollectionViewDelegateFlowLayout, LXReorderableCollectionViewDataSource, LXReorderableCollectionViewDelegateFlowLayout, UITableViewDataSource, UITableViewDelegate, MLMediaLibrary, VLCMediaListDelegate, UISearchBarDelegate, UISearchDisplayDelegate> {
  46. NSMutableArray *_foundMedia;
  47. VLCLibraryMode _libraryMode;
  48. VLCLibraryMode _previousLibraryMode;
  49. UIBarButtonItem *_menuButton;
  50. NSMutableArray *_indexPaths;
  51. id _folderObject;
  52. VLCFolderCollectionViewFlowLayout *_folderLayout;
  53. LXReorderableCollectionViewFlowLayout *_reorderLayout;
  54. BOOL _inFolder;
  55. BOOL _isSelected;
  56. BOOL _deleteFromTableView;
  57. UILongPressGestureRecognizer *_longPressGestureRecognizer;
  58. NSMutableArray *_searchData;
  59. UISearchBar *_searchBar;
  60. UISearchDisplayController *_searchDisplayController;
  61. UIBarButtonItem *_selectAllBarButtonItem;
  62. UIBarButtonItem *_createFolderBarButtonItem;
  63. UIBarButtonItem *_openInActivityBarButtonItem;
  64. UIBarButtonItem *_removeFromFolderBarButtonItem;
  65. UIBarButtonItem *_deleteSelectedBarButtonItem;
  66. VLCOpenInActivity *_openInActivity;
  67. }
  68. @property (nonatomic, strong) UIBarButtonItem *displayModeBarButtonItem;
  69. @property (nonatomic, strong) UITableView *tableView;
  70. @property (nonatomic, strong) UICollectionView *collectionView;
  71. @property (nonatomic, strong) EmptyLibraryView *emptyLibraryView;
  72. @property (nonatomic) BOOL usingTableViewToShowData;
  73. @end
  74. @implementation VLCLibraryViewController
  75. - (void)dealloc
  76. {
  77. }
  78. + (void)initialize
  79. {
  80. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  81. [defaults registerDefaults:@{kDisplayedFirstSteps : [NSNumber numberWithBool:NO]}];
  82. [defaults registerDefaults:@{kUsingTableViewToShowData : [NSNumber numberWithBool:UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone]}];
  83. }
  84. - (void)loadView
  85. {
  86. [self setupContentViewWithContentInset:NO];
  87. [self setViewFromDeviceOrientation];
  88. [self updateViewsForCurrentDisplayMode];
  89. _libraryMode = VLCLibraryModeAllFiles;
  90. self.emptyLibraryView = [[[NSBundle mainBundle] loadNibNamed:@"VLCEmptyLibraryView" owner:self options:nil] lastObject];
  91. _emptyLibraryView.emptyLibraryLongDescriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
  92. _emptyLibraryView.emptyLibraryLongDescriptionLabel.numberOfLines = 0;
  93. }
  94. - (void)setupContentViewWithContentInset:(BOOL)setInset
  95. {
  96. CGRect viewDimensions = [UIApplication sharedApplication].keyWindow.bounds;
  97. UIView *contentView = [[UIView alloc] initWithFrame:viewDimensions];
  98. contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  99. contentView.backgroundColor = [UIColor VLCDarkBackgroundColor];
  100. if (self.usingTableViewToShowData) {
  101. if(!_tableView) {
  102. _tableView = [[UITableView alloc] initWithFrame:viewDimensions style:UITableViewStylePlain];
  103. _tableView.backgroundColor = [UIColor VLCDarkBackgroundColor];
  104. CGRect frame = _tableView.bounds;
  105. frame.origin.y = -frame.size.height;
  106. UIView *topView = [[UIView alloc] initWithFrame:frame];
  107. topView.backgroundColor = [UIColor VLCDarkBackgroundColor];
  108. [_tableView addSubview:topView];
  109. _tableView.rowHeight = [VLCPlaylistTableViewCell heightOfCell];
  110. _tableView.separatorColor = [UIColor VLCDarkBackgroundColor];
  111. _tableView.delegate = self;
  112. _tableView.dataSource = self;
  113. _tableView.opaque = YES;
  114. _tableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
  115. _tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  116. _tableView.tableHeaderView = _searchBar;
  117. _tableView.tableFooterView = [UIView new];
  118. self.navigationController.scrollNavigationBar.scrollView = self.tableView;
  119. }
  120. _tableView.frame = contentView.bounds;
  121. [contentView addSubview:_tableView];
  122. [_tableView reloadData];
  123. } else {
  124. if (!_collectionView) {
  125. _folderLayout = [[VLCFolderCollectionViewFlowLayout alloc] init];
  126. _collectionView = [[UICollectionView alloc] initWithFrame:viewDimensions collectionViewLayout:_folderLayout];
  127. _collectionView.alwaysBounceVertical = YES;
  128. _collectionView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
  129. _collectionView.delegate = self;
  130. _collectionView.dataSource = self;
  131. _collectionView.opaque = YES;
  132. _collectionView.backgroundColor = [UIColor VLCDarkBackgroundColor];
  133. _collectionView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  134. _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_collectionViewHandleLongPressGesture:)];
  135. [_collectionView addGestureRecognizer:_longPressGestureRecognizer];
  136. [_collectionView registerNib:[UINib nibWithNibName:@"VLCPlaylistCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"PlaylistCell"];
  137. self.navigationController.scrollNavigationBar.scrollView = self.collectionView;
  138. }
  139. _collectionView.frame = contentView.bounds;
  140. [contentView addSubview:_collectionView];
  141. [_collectionView reloadData];
  142. }
  143. self.view = contentView;
  144. }
  145. #pragma mark -
  146. - (void)viewWillLayoutSubviews {
  147. UIScrollView *dataView = self.usingTableViewToShowData ? _tableView : _collectionView;
  148. [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
  149. [dataView setContentInset:UIEdgeInsetsZero];
  150. }
  151. - (void)viewDidLoad
  152. {
  153. [super viewDidLoad];
  154. self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", nil);
  155. _menuButton = [UIBarButtonItem themedRevealMenuButtonWithTarget:self andSelector:@selector(leftButtonAction:)];
  156. self.navigationItem.leftBarButtonItem = _menuButton;
  157. self.editButtonItem.title = NSLocalizedString(@"BUTTON_EDIT", nil);
  158. self.editButtonItem.tintColor = [UIColor whiteColor];
  159. _selectAllBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"BUTTON_ALL", nil) style:UIBarButtonItemStylePlain target:self action:@selector(handleSelection)];
  160. _selectAllBarButtonItem.tintColor = [UIColor whiteColor];
  161. UIFont *font = [UIFont boldSystemFontOfSize:17];
  162. NSDictionary *attributes = @{NSFontAttributeName: font};
  163. [_selectAllBarButtonItem setTitleTextAttributes:attributes forState:UIControlStateNormal];
  164. _emptyLibraryView.emptyLibraryLabel.text = NSLocalizedString(@"EMPTY_LIBRARY", nil);
  165. _emptyLibraryView.emptyLibraryLongDescriptionLabel.text = NSLocalizedString(@"EMPTY_LIBRARY_LONG", nil);
  166. [_emptyLibraryView.emptyLibraryLongDescriptionLabel sizeToFit];
  167. [_emptyLibraryView.learnMoreButton setTitle:NSLocalizedString(@"BUTTON_LEARN_MORE", nil) forState:UIControlStateNormal];
  168. _createFolderBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemOrganize target:self action:@selector(createFolder)];
  169. // Better visual alignment with the action button
  170. _createFolderBarButtonItem.imageInsets = UIEdgeInsetsMake(4, 0, -4, 0);
  171. _createFolderBarButtonItem.landscapeImagePhoneInsets = UIEdgeInsetsMake(3, 0, -3, 0);
  172. _removeFromFolderBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemReply target:self action:@selector(removeFromFolder)];
  173. _removeFromFolderBarButtonItem.imageInsets = UIEdgeInsetsMake(4, 0, -4, 0);
  174. _removeFromFolderBarButtonItem.landscapeImagePhoneInsets = UIEdgeInsetsMake(3, 0, -3, 0);
  175. _removeFromFolderBarButtonItem.enabled = NO;
  176. _openInActivityBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(actOnSelection:)];
  177. _openInActivityBarButtonItem.enabled = NO;
  178. _deleteSelectedBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteSelection:)];
  179. _deleteFromTableView = NO;
  180. UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
  181. fixedSpace.width = 20;
  182. UIBarButtonItem *secondFixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
  183. fixedSpace.width = 20;
  184. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  185. fixedSpace.width *= 2;
  186. }
  187. [self setToolbarItems:@[_openInActivityBarButtonItem,
  188. fixedSpace,
  189. _createFolderBarButtonItem,
  190. [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil],
  191. [UIBarButtonItem themedDarkToolbarButtonWithTitle:NSLocalizedString(@"BUTTON_RENAME", nil) target:self andSelector:@selector(renameSelection)],
  192. [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil],
  193. _removeFromFolderBarButtonItem,
  194. secondFixedSpace,
  195. _deleteSelectedBarButtonItem]];
  196. self.navigationController.toolbar.barStyle = UIBarStyleBlack;
  197. self.navigationController.toolbar.tintColor = [UIColor whiteColor];
  198. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
  199. _searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
  200. UINavigationBar *navBar = self.navigationController.navigationBar;
  201. _searchBar.barTintColor = navBar.barTintColor;
  202. // cancel button tint color of UISearchBar with white color
  203. [[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIColor whiteColor], NSForegroundColorAttributeName, nil] forState:UIControlStateNormal];
  204. _searchBar.tintColor = navBar.tintColor;
  205. _searchBar.translucent = navBar.translucent;
  206. _searchBar.opaque = navBar.opaque;
  207. _searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:_searchBar contentsController:self];
  208. _searchDisplayController.delegate = self;
  209. _searchDisplayController.searchResultsDataSource = self;
  210. _searchDisplayController.searchResultsDelegate = self;
  211. _searchDisplayController.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  212. _searchDisplayController.searchResultsTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
  213. _searchBar.delegate = self;
  214. [self setSearchBar:YES resetContent:YES];
  215. self.edgesForExtendedLayout = UIRectEdgeNone;
  216. @synchronized (self) {
  217. _searchData = [[NSMutableArray alloc] init];
  218. }
  219. }
  220. - (void)viewWillAppear:(BOOL)animated
  221. {
  222. [super viewWillAppear:animated];
  223. [self.collectionView.collectionViewLayout invalidateLayout];
  224. [self _displayEmptyLibraryViewIfNeeded];
  225. [self enableNavigationBarAnimation:YES resetPositionWithAnimation:YES];
  226. }
  227. - (void)viewDidAppear:(BOOL)animated
  228. {
  229. [super viewDidAppear:animated];
  230. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  231. if (![[defaults objectForKey:kDisplayedFirstSteps] boolValue]) {
  232. [self.emptyLibraryView performSelector:@selector(learnMore:) withObject:nil afterDelay:1.];
  233. [defaults setObject:[NSNumber numberWithBool:YES] forKey:kDisplayedFirstSteps];
  234. [defaults synchronize];
  235. }
  236. if (_foundMedia.count < 1)
  237. [self updateViewContents];
  238. [[MLMediaLibrary sharedMediaLibrary] performSelector:@selector(libraryDidAppear) withObject:nil afterDelay:1.];
  239. }
  240. - (void)viewDidDisappear:(BOOL)animated
  241. {
  242. [super viewDidDisappear:animated];
  243. [[MLMediaLibrary sharedMediaLibrary] libraryDidDisappear];
  244. }
  245. - (BOOL)canBecomeFirstResponder
  246. {
  247. return YES;
  248. }
  249. - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
  250. {
  251. if (motion == UIEventSubtypeMotionShake)
  252. [[VLCBugreporter sharedInstance] handleBugreportRequest];
  253. }
  254. - (void)openMediaObject:(NSManagedObject *)mediaObject
  255. {
  256. if ([mediaObject isKindOfClass:[MLAlbum class]] || [mediaObject isKindOfClass:[MLShow class]]) {
  257. BOOL isAlbum = [mediaObject isKindOfClass:[MLAlbum class]];
  258. NSArray* array = isAlbum ? [(MLAlbum *)mediaObject sortedTracks] : [(MLShow *)mediaObject sortedEpisodes];
  259. @synchronized(_foundMedia) {
  260. _foundMedia = [NSMutableArray arrayWithArray:array];
  261. }
  262. self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedBackButtonWithTarget:self andSelector:@selector(backToAllItems:)];
  263. if (_libraryMode == VLCLibraryModeAllFiles)
  264. self.navigationItem.leftBarButtonItem.title = NSLocalizedString(@"BUTTON_BACK", nil);
  265. else
  266. [self.navigationItem.leftBarButtonItem setTitle: isAlbum ? NSLocalizedString(@"LIBRARY_MUSIC", nil) : NSLocalizedString(@"LIBRARY_SERIES", nil)];
  267. self.title = [(MLAlbum*)mediaObject name];
  268. [self reloadViews];
  269. } else if ([mediaObject isKindOfClass:[MLLabel class]]) {
  270. MLLabel *folder = (MLLabel*) mediaObject;
  271. _inFolder = YES;
  272. [self.navigationController.scrollNavigationBar resetToDefaultPositionWithAnimation:YES];
  273. if (!self.usingTableViewToShowData) {
  274. if (![self.collectionView.collectionViewLayout isEqual:_reorderLayout]) {
  275. for (UIGestureRecognizer *recognizer in _collectionView.gestureRecognizers) {
  276. if (recognizer == _folderLayout.panGestureRecognizer || recognizer == _folderLayout.longPressGestureRecognizer || recognizer == _longPressGestureRecognizer)
  277. [self.collectionView removeGestureRecognizer:recognizer];
  278. }
  279. _reorderLayout = [[LXReorderableCollectionViewFlowLayout alloc] init];
  280. [self.collectionView setCollectionViewLayout:_reorderLayout animated:NO];
  281. }
  282. }
  283. _libraryMode = VLCLibraryModeFolder;
  284. @synchronized(_foundMedia) {
  285. _foundMedia = [NSMutableArray arrayWithArray:[folder sortedFolderItems]];
  286. }
  287. self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedBackButtonWithTarget:self andSelector:@selector(backToAllItems:)];
  288. self.navigationItem.leftBarButtonItem.title = NSLocalizedString(@"BUTTON_BACK", nil);
  289. self.title = [folder name];
  290. _removeFromFolderBarButtonItem.enabled = YES;
  291. _createFolderBarButtonItem.enabled = NO;
  292. [self reloadViews];
  293. return;
  294. } else {
  295. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  296. [vpc playMediaLibraryObject:mediaObject];
  297. [self createSpotlightItem:mediaObject];
  298. }
  299. }
  300. - (void)createSpotlightItem:(nonnull NSManagedObject *)mediaObject
  301. {
  302. if ([CSSearchableItemAttributeSet class] != nil && ![[VLCKeychainCoordinator defaultCoordinator] passcodeLockEnabled]) {
  303. self.userActivity = [[NSUserActivity alloc] initWithActivityType:kVLCUserActivityPlaying];
  304. MLFile *file = nil;
  305. if ([mediaObject isKindOfClass:[MLAlbumTrack class]]) {
  306. file = [(MLAlbumTrack *)mediaObject anyFileFromTrack];
  307. } else if ([mediaObject isKindOfClass:[MLShowEpisode class]]) {
  308. file = [(MLShowEpisode *)mediaObject anyFileFromEpisode];
  309. } else if ([mediaObject isKindOfClass:[MLFile class]]){
  310. file = (MLFile *)mediaObject;
  311. }
  312. self.userActivity.title = file.title;
  313. self.userActivity.contentAttributeSet = file.coreSpotlightAttributeSet;
  314. self.userActivity.userInfo = @{@"playingmedia":mediaObject.objectID.URIRepresentation};
  315. self.userActivity.eligibleForSearch = YES;
  316. self.userActivity.eligibleForHandoff = YES;
  317. //self.userActivity.contentUserAction = NSUserActivityContentUserActionPlay;
  318. [self.userActivity becomeCurrent];
  319. }
  320. }
  321. - (void)removeMediaObject:(id)managedObject updateDatabase:(BOOL)updateDB
  322. {
  323. // delete all tracks from an album
  324. if ([managedObject isKindOfClass:[MLAlbum class]]) {
  325. MLAlbum *album = managedObject;
  326. NSSet *iterAlbumTrack = [NSSet setWithSet:album.tracks];
  327. for (MLAlbumTrack *track in iterAlbumTrack) {
  328. NSSet *iterFiles = [NSSet setWithSet:track.files];
  329. for (MLFile *file in iterFiles)
  330. [self _deleteMediaObject:file];
  331. }
  332. [[MLMediaLibrary sharedMediaLibrary] removeObject: album];
  333. // delete all episodes from a show
  334. } else if ([managedObject isKindOfClass:[MLShow class]]) {
  335. MLShow *show = managedObject;
  336. NSSet *iterShowEpisodes = [NSSet setWithSet:show.episodes];
  337. for (MLShowEpisode *episode in iterShowEpisodes) {
  338. NSSet *iterFiles = [NSSet setWithSet:episode.files];
  339. for (MLFile *file in iterFiles)
  340. [self _deleteMediaObject:file];
  341. }
  342. [[MLMediaLibrary sharedMediaLibrary] removeObject: show];
  343. // delete all files from an episode
  344. } else if ([managedObject isKindOfClass:[MLShowEpisode class]]) {
  345. MLShowEpisode *episode = managedObject;
  346. NSSet *iterFiles = [NSSet setWithSet:episode.files];
  347. for (MLFile *file in iterFiles)
  348. [self _deleteMediaObject:file];
  349. // delete all files from a track
  350. [[MLMediaLibrary sharedMediaLibrary] removeObject: episode];
  351. } else if ([managedObject isKindOfClass:[MLAlbumTrack class]]) {
  352. MLAlbumTrack *track = managedObject;
  353. NSSet *iterFiles = [NSSet setWithSet:track.files];
  354. for (MLFile *file in iterFiles)
  355. [self _deleteMediaObject:file];
  356. } else if ([managedObject isKindOfClass:[MLLabel class]]) {
  357. MLLabel *folder = managedObject;
  358. NSSet *iterFiles = [NSSet setWithSet:folder.files];
  359. [folder removeFiles:folder.files];
  360. for (MLFile *file in iterFiles)
  361. [self _deleteMediaObject:file];
  362. [[MLMediaLibrary sharedMediaLibrary] removeObject:folder];
  363. }
  364. else
  365. [self _deleteMediaObject:managedObject];
  366. if (updateDB) {
  367. [[MLMediaLibrary sharedMediaLibrary] updateMediaDatabase];
  368. [self updateViewContents];
  369. }
  370. }
  371. - (void)_deleteMediaObject:(MLFile *)mediaObject
  372. {
  373. if (_inFolder)
  374. [self rearrangeFolderTrackNumbersForRemovedItem:mediaObject];
  375. /* stop playback if needed */
  376. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  377. if (vpc.isPlaying) {
  378. MLFile *currentlyPlayingFile = [[MLFile fileForURL:vpc.mediaPlayer.media.url] firstObject];
  379. if (currentlyPlayingFile) {
  380. if (currentlyPlayingFile == mediaObject)
  381. [vpc stopPlayback];
  382. }
  383. }
  384. NSFileManager *fileManager = [NSFileManager defaultManager];
  385. NSString *folderLocation = [[mediaObject.url path] stringByDeletingLastPathComponent];
  386. NSArray *allfiles = [fileManager contentsOfDirectoryAtPath:folderLocation error:nil];
  387. NSString *fileName = [mediaObject.path.lastPathComponent stringByDeletingPathExtension];
  388. if (!fileName)
  389. return;
  390. NSIndexSet *indexSet = [allfiles indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
  391. return ([obj rangeOfString:fileName].location != NSNotFound);
  392. }];
  393. NSUInteger count = indexSet.count;
  394. NSString *additionalFilePath;
  395. NSUInteger currentIndex = [indexSet firstIndex];
  396. for (unsigned int x = 0; x < count; x++) {
  397. additionalFilePath = allfiles[currentIndex];
  398. if ([additionalFilePath isSupportedSubtitleFormat])
  399. [fileManager removeItemAtPath:[folderLocation stringByAppendingPathComponent:additionalFilePath] error:nil];
  400. currentIndex = [indexSet indexGreaterThanIndex:currentIndex];
  401. }
  402. [fileManager removeItemAtURL:mediaObject.url error:nil];
  403. }
  404. - (void)_displayEmptyLibraryViewIfNeeded
  405. {
  406. if (self.emptyLibraryView.superview)
  407. [self.emptyLibraryView removeFromSuperview];
  408. if (_foundMedia.count == 0) {
  409. _inFolder = (_libraryMode == VLCLibraryModeFolder || _libraryMode == VLCLibraryModeCreateFolder);
  410. self.emptyLibraryView.emptyLibraryLabel.text = _inFolder ? NSLocalizedString(@"FOLDER_EMPTY", nil) : NSLocalizedString(@"EMPTY_LIBRARY", nil);
  411. self.emptyLibraryView.emptyLibraryLongDescriptionLabel.text = _inFolder ? NSLocalizedString(@"FOLDER_EMPTY_LONG", nil) : NSLocalizedString(@"EMPTY_LIBRARY_LONG", nil);
  412. self.emptyLibraryView.learnMoreButton.hidden = _inFolder;
  413. self.emptyLibraryView.frame = self.view.bounds;
  414. [self.view addSubview:self.emptyLibraryView];
  415. self.navigationItem.rightBarButtonItems = nil;
  416. } else {
  417. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  418. UIBarButtonItem *toggleDisplayedView = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"tableViewIcon"] style:UIBarButtonItemStylePlain target:self action:@selector(toggleDisplayedView:)];
  419. self.navigationItem.rightBarButtonItems = @[toggleDisplayedView, self.editButtonItem];
  420. self.displayModeBarButtonItem = toggleDisplayedView;
  421. } else {
  422. self.navigationItem.rightBarButtonItem = self.editButtonItem;
  423. }
  424. }
  425. if (self.usingTableViewToShowData)
  426. _tableView.separatorStyle = (_foundMedia.count > 0)? UITableViewCellSeparatorStyleSingleLine:
  427. UITableViewCellSeparatorStyleNone;
  428. else
  429. [self.collectionView.collectionViewLayout invalidateLayout];
  430. [self updateViewsForCurrentDisplayMode];
  431. }
  432. - (void)setViewFromDeviceOrientation
  433. {
  434. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  435. UIDevice *currentDevice = [UIDevice currentDevice];
  436. BOOL isPortrait = UIDeviceOrientationIsPortrait(currentDevice.orientation);
  437. if (self.isEditing) {
  438. [self setEditing:NO animated:NO];
  439. }
  440. [self setUsingTableViewToShowData:isPortrait];
  441. [self enableNavigationBarAnimation:YES resetPositionWithAnimation:YES];
  442. }
  443. }
  444. - (void)setSearchBar:(BOOL)enable resetContent:(BOOL)resetContent
  445. {
  446. if (enable)
  447. self.tableView.tableHeaderView = _searchBar;
  448. else
  449. self.tableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.tableView.bounds.size.width, CGFLOAT_MIN)];
  450. if (resetContent) {
  451. CGPoint contentOffset = self.tableView.contentOffset;
  452. contentOffset.y += CGRectGetHeight(self.tableView.tableHeaderView.frame);
  453. self.tableView.contentOffset = contentOffset;
  454. }
  455. }
  456. - (void)enableNavigationBarAnimation:(BOOL)enable resetPositionWithAnimation:(BOOL)resetPositionWithAnimation
  457. {
  458. if (!enable) {
  459. self.navigationController.scrollNavigationBar.scrollView = nil;
  460. } else {
  461. self.navigationController.scrollNavigationBar.scrollView = self.usingTableViewToShowData ? self.tableView : self.collectionView;
  462. }
  463. if (resetPositionWithAnimation) {
  464. [self.navigationController.scrollNavigationBar resetToDefaultPositionWithAnimation:YES];
  465. }
  466. }
  467. - (void)libraryUpgradeComplete
  468. {
  469. self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", nil);
  470. self.navigationItem.leftBarButtonItem = _menuButton;
  471. self.emptyLibraryView.emptyLibraryLongDescriptionLabel.hidden = NO;
  472. self.emptyLibraryView.emptyLibraryLabel.text = NSLocalizedString(@"EMPTY_LIBRARY", nil);
  473. [self.emptyLibraryView.activityIndicator stopAnimating];
  474. [self.emptyLibraryView removeFromSuperview];
  475. [self updateViewContents];
  476. }
  477. - (void)updateViewContents
  478. {
  479. @synchronized(self) {
  480. _foundMedia = [[NSMutableArray alloc] init];
  481. }
  482. if (![(VLCAppDelegate *)[UIApplication sharedApplication].delegate passcodeValidated]) {
  483. APLog(@"library is locked, won't show contents");
  484. return;
  485. }
  486. self.navigationItem.leftBarButtonItem = _menuButton;
  487. if (_libraryMode == VLCLibraryModeAllAlbums)
  488. self.title = NSLocalizedString(@"LIBRARY_MUSIC", nil);
  489. else if( _libraryMode == VLCLibraryModeAllSeries)
  490. self.title = NSLocalizedString(@"LIBRARY_SERIES", nil);
  491. else
  492. self.title = NSLocalizedString(@"LIBRARY_ALL_FILES", nil);
  493. _createFolderBarButtonItem.enabled = (_libraryMode == VLCLibraryModeAllAlbums || _libraryMode == VLCLibraryModeAllSeries) ? NO : YES;
  494. _removeFromFolderBarButtonItem.enabled = NO;
  495. /* add all albums */
  496. if (_libraryMode != VLCLibraryModeAllSeries) {
  497. NSArray *rawAlbums = [MLAlbum allAlbums];
  498. for (MLAlbum *album in rawAlbums) {
  499. if (_libraryMode != VLCLibraryModeAllAlbums) {
  500. if (album.name.length > 0 && album.tracks.count > 1) {
  501. @synchronized(_foundMedia) {
  502. [_foundMedia addObject:album];
  503. }
  504. }
  505. } else {
  506. if (album.name.length > 0) {
  507. @synchronized(_foundMedia) {
  508. [_foundMedia addObject:album];
  509. }
  510. }
  511. }
  512. }
  513. }
  514. if (_libraryMode == VLCLibraryModeAllAlbums) {
  515. [self reloadViews];
  516. return;
  517. }
  518. /* add all shows */
  519. NSArray *rawShows = [MLShow allShows];
  520. for (MLShow *show in rawShows) {
  521. if (show.name.length > 0 && show.episodes.count > 1) {
  522. @synchronized(_foundMedia) {
  523. [_foundMedia addObject:show];
  524. }
  525. }
  526. }
  527. if (_libraryMode == VLCLibraryModeAllSeries) {
  528. [self reloadViews];
  529. return;
  530. }
  531. /* add all folders*/
  532. NSArray *allFolders = [MLLabel allLabels];
  533. for (MLLabel *folder in allFolders) {
  534. @synchronized(_foundMedia) {
  535. [_foundMedia addObject:folder];
  536. }
  537. }
  538. /* add all remaining files */
  539. NSArray *allFiles = [MLFile allFiles];
  540. for (MLFile *file in allFiles) {
  541. if (file.labels != nil) {
  542. @synchronized(file.labels) {
  543. if (file.labels.count > 0)
  544. continue;
  545. }
  546. }
  547. if (!file.isShowEpisode && !file.isAlbumTrack) {
  548. @synchronized(_foundMedia) {
  549. [_foundMedia addObject:file];
  550. }
  551. }
  552. else if (file.isShowEpisode) {
  553. if (file.showEpisode.show.episodes.count < 2) {
  554. @synchronized(_foundMedia) {
  555. [_foundMedia addObject:file];
  556. }
  557. }
  558. /* older MediaLibraryKit versions don't send a show name in a popular
  559. * corner case. hence, we need to work-around here and force a reload
  560. * afterwards as this could lead to the 'all my shows are gone'
  561. * syndrome (see #10435, #10464, #10432 et al) */
  562. if (file.showEpisode.show.name.length == 0) {
  563. file.showEpisode.show.name = NSLocalizedString(@"UNTITLED_SHOW", nil);
  564. [self performSelector:@selector(updateViewContents) withObject:nil afterDelay:0.1];
  565. }
  566. } else if (file.isAlbumTrack) {
  567. if (file.albumTrack.album.tracks.count < 2) {
  568. @synchronized(_foundMedia) {
  569. [_foundMedia addObject:file];
  570. }
  571. }
  572. }
  573. }
  574. [self reloadViews];
  575. }
  576. - (void)reloadViews
  577. {
  578. // 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.
  579. if (self.usingTableViewToShowData) {
  580. [self.tableView reloadData];
  581. [self updateActionBarButtonItemStateWithSelectedIndexPaths:[self.tableView indexPathsForSelectedRows]];
  582. _isSelected = NO;
  583. } else {
  584. [self.collectionView reloadData];
  585. [self updateActionBarButtonItemStateWithSelectedIndexPaths:[self.collectionView indexPathsForSelectedItems]];
  586. if (_libraryMode == VLCLibraryModeAllFiles) {
  587. if (self.collectionView.collectionViewLayout != _folderLayout) {
  588. for (UIGestureRecognizer *recognizer in _collectionView.gestureRecognizers) {
  589. if (recognizer == _reorderLayout.panGestureRecognizer ||
  590. recognizer == _reorderLayout.longPressGestureRecognizer) {
  591. [self.collectionView removeGestureRecognizer:recognizer];
  592. }
  593. }
  594. [self.collectionView setCollectionViewLayout:_folderLayout animated:NO];
  595. [self.collectionView addGestureRecognizer:_longPressGestureRecognizer];
  596. }
  597. }
  598. }
  599. [self _displayEmptyLibraryViewIfNeeded];
  600. }
  601. #pragma mark - Table View
  602. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  603. {
  604. return 1;
  605. }
  606. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  607. {
  608. if (tableView == self.searchDisplayController.searchResultsTableView)
  609. return _searchData.count;
  610. return _foundMedia.count;
  611. }
  612. // Customize the appearance of table view cells.
  613. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  614. {
  615. static NSString *CellIdentifier = @"PlaylistCell";
  616. VLCPlaylistTableViewCell *cell = (VLCPlaylistTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  617. if (cell == nil)
  618. cell = [VLCPlaylistTableViewCell cellWithReuseIdentifier:CellIdentifier];
  619. else
  620. [cell collapsWithAnimation:NO];
  621. UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRightOnTableViewCellGestureAction:)];
  622. [swipeRight setDirection:(UISwipeGestureRecognizerDirectionRight)];
  623. [cell addGestureRecognizer:swipeRight];
  624. NSInteger row = indexPath.row;
  625. if (tableView == self.searchDisplayController.searchResultsTableView) {
  626. @synchronized (_searchData) {
  627. if (row < _searchData.count)
  628. cell.mediaObject = _searchData[row];
  629. }
  630. } else {
  631. @synchronized (_foundMedia) {
  632. if (row < _foundMedia.count)
  633. cell.mediaObject = _foundMedia[row];
  634. }
  635. }
  636. return cell;
  637. }
  638. - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
  639. {
  640. @synchronized(_foundMedia) {
  641. MLFile* object = _foundMedia[fromIndexPath.item];
  642. [_foundMedia removeObjectAtIndex:fromIndexPath.item];
  643. [_foundMedia insertObject:object atIndex:toIndexPath.item];
  644. if (![object isKindOfClass:[MLFile class]])
  645. return;
  646. object.folderTrackNumber = @(toIndexPath.item - 1);
  647. object = [_foundMedia objectAtIndex:fromIndexPath.item];
  648. if (![object isKindOfClass:[MLFile class]])
  649. return;
  650. object.folderTrackNumber = @(fromIndexPath.item - 1);
  651. }
  652. }
  653. - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
  654. {
  655. return _inFolder;
  656. }
  657. - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
  658. {
  659. cell.backgroundColor = (indexPath.row % 2 == 0)? [UIColor blackColor]: [UIColor VLCDarkBackgroundColor];
  660. cell.multipleSelectionBackgroundView.backgroundColor = cell.backgroundColor;
  661. }
  662. - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
  663. {
  664. return YES;
  665. }
  666. - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
  667. {
  668. if (editingStyle == UITableViewCellEditingStyleDelete) {
  669. NSInteger row = indexPath.row;
  670. _deleteFromTableView = YES;
  671. if (row < _foundMedia.count)
  672. [self deleteSelection:indexPath];
  673. }
  674. }
  675. - (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
  676. {
  677. if (tableView.isEditing) {
  678. [self updateActionBarButtonItemStateWithSelectedIndexPaths:[tableView indexPathsForSelectedRows]];
  679. }
  680. }
  681. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  682. {
  683. if ([(VLCPlaylistTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] isExpanded]) {
  684. [(VLCPlaylistTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] collapsWithAnimation:YES];
  685. return;
  686. }
  687. NSArray *visibleCells = [tableView visibleCells];
  688. NSUInteger cellCount = visibleCells.count;
  689. for (NSUInteger x = 0; x < cellCount; x++) {
  690. if ([visibleCells[x] isExpanded])
  691. [visibleCells[x] collapsWithAnimation:NO];
  692. }
  693. if (tableView.isEditing) {
  694. if (_libraryMode == VLCLibraryModeCreateFolder) {
  695. _folderObject = _foundMedia[indexPath.row];
  696. _libraryMode = _previousLibraryMode;
  697. [self updateViewContents];
  698. [self createFolderWithName:nil];
  699. } else {
  700. [self updateActionBarButtonItemStateWithSelectedIndexPaths:[tableView indexPathsForSelectedRows]];
  701. }
  702. return;
  703. }
  704. [tableView deselectRowAtIndexPath:indexPath animated:YES];
  705. NSManagedObject *selectedObject;
  706. NSUInteger row = indexPath.row;
  707. if (tableView == self.searchDisplayController.searchResultsTableView) {
  708. @synchronized (_searchData) {
  709. if (row < _searchData.count)
  710. selectedObject = _searchData[row];
  711. }
  712. } else {
  713. @synchronized (_foundMedia) {
  714. if (row < _foundMedia.count)
  715. selectedObject = _foundMedia[row];
  716. }
  717. }
  718. if (_searchDisplayController.active)
  719. [_searchDisplayController setActive:NO animated:NO];
  720. if (selectedObject != nil)
  721. [self openMediaObject:selectedObject];
  722. }
  723. #pragma mark - Gesture Action
  724. - (void)swipeRightOnTableViewCellGestureAction:(UIGestureRecognizer *)recognizer
  725. {
  726. if (self.tableView.isEditing) {
  727. [self setEditing:NO animated:YES];
  728. } else {
  729. [self setEditing:YES animated:YES];
  730. NSIndexPath *path = [self.tableView indexPathForRowAtPoint:[recognizer locationInView:self.tableView]];
  731. [self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:path.row inSection:path.section]
  732. animated:YES
  733. scrollPosition:UITableViewScrollPositionNone];
  734. [self updateActionBarButtonItemStateWithSelectedIndexPaths:[self.tableView indexPathsForSelectedRows]];
  735. }
  736. }
  737. - (void)swipeRightOnCollectionViewCellGestureAction:(UIGestureRecognizer *)recognizer
  738. {
  739. NSIndexPath *path = [self.collectionView indexPathForItemAtPoint:[recognizer locationInView:self.collectionView]];
  740. VLCPlaylistCollectionViewCell *cell = (VLCPlaylistCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:path];
  741. [cell showMetadata:!cell.showsMetaData];
  742. }
  743. #pragma mark - Collection View
  744. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
  745. {
  746. return _foundMedia.count;
  747. }
  748. - (UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
  749. {
  750. VLCPlaylistCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"PlaylistCell" forIndexPath:indexPath];
  751. @synchronized (_foundMedia) {
  752. NSUInteger row = indexPath.row;
  753. if (row < _foundMedia.count)
  754. cell.mediaObject = _foundMedia[row];
  755. }
  756. cell.collectionView = _collectionView;
  757. [cell setEditing:self.editing animated:NO];
  758. UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRightOnCollectionViewCellGestureAction:)];
  759. [swipeRight setDirection:(UISwipeGestureRecognizerDirectionRight)];
  760. [cell addGestureRecognizer:swipeRight];
  761. return cell;
  762. }
  763. - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
  764. {
  765. const CGFloat maxCellWidth = [UIScreen mainScreen].bounds.size.width / 3;
  766. const CGFloat aspectRatio = 9.0/16.0;
  767. CGRect windowFrame = [UIApplication sharedApplication].keyWindow.frame;
  768. CGFloat windowWidth = windowFrame.size.width;
  769. int numberOfCellsPerRow = ceil(windowWidth/maxCellWidth);
  770. CGFloat cellWidth = windowWidth/numberOfCellsPerRow;
  771. cellWidth -= 5;
  772. return CGSizeMake(cellWidth, cellWidth * aspectRatio);
  773. }
  774. - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
  775. {
  776. return UIEdgeInsetsMake(5, 5, 5, 5);
  777. }
  778. - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
  779. {
  780. return 2.5;
  781. }
  782. - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
  783. {
  784. return 0.;
  785. }
  786. - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
  787. {
  788. if (self.editing) {
  789. if (_libraryMode == VLCLibraryModeCreateFolder) {
  790. _folderObject = _foundMedia[indexPath.item];
  791. [self updateViewContents];
  792. [self createFolderWithName:nil];
  793. _libraryMode = _previousLibraryMode;
  794. } else {
  795. [self updateActionBarButtonItemStateWithSelectedIndexPaths:[collectionView indexPathsForSelectedItems]];
  796. }
  797. [(VLCPlaylistCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath] selectionUpdate];
  798. return;
  799. }
  800. [collectionView deselectItemAtIndexPath:indexPath animated:NO];
  801. NSArray *visibleCells = [[collectionView visibleCells] copy];
  802. NSUInteger cellCount = visibleCells.count;
  803. for (NSUInteger x = 0; x < cellCount; x++) {
  804. VLCPlaylistCollectionViewCell *cell = visibleCells[x];
  805. if ([cell showsMetaData])
  806. [cell showMetadata:NO];
  807. }
  808. NSManagedObject *selectedObject;
  809. NSInteger row = indexPath.row;
  810. @synchronized(_foundMedia) {
  811. if (row < _foundMedia.count)
  812. selectedObject = _foundMedia[row];
  813. }
  814. if (selectedObject != nil)
  815. [self openMediaObject:selectedObject];
  816. }
  817. - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
  818. {
  819. if (self.editing) {
  820. [self updateActionBarButtonItemStateWithSelectedIndexPaths:[collectionView indexPathsForSelectedItems]];
  821. }
  822. [(VLCPlaylistCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath] selectionUpdate];
  823. }
  824. - (void)collectionView:(UICollectionView *)collectionView removeItemFromFolderAtIndexPathIfNeeded:(NSIndexPath *)indexPath
  825. {
  826. id mediaObject;
  827. @synchronized(_foundMedia) {
  828. mediaObject = _foundMedia[indexPath.item];
  829. }
  830. if (![mediaObject isKindOfClass:[MLFile class]])
  831. return;
  832. MLFile *mediaFile = (MLFile *)mediaObject;
  833. [self rearrangeFolderTrackNumbersForRemovedItem:mediaFile];
  834. mediaFile.labels = nil;
  835. mediaFile.folderTrackNumber = nil;
  836. [self backToAllItems:nil];
  837. }
  838. - (void)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath willMoveToIndexPath:(NSIndexPath *)toIndexPath
  839. {
  840. @synchronized(_foundMedia) {
  841. id object = _foundMedia[fromIndexPath.item];
  842. if (![object isKindOfClass:[MLFile class]])
  843. return;
  844. [_foundMedia removeObjectAtIndex:fromIndexPath.item];
  845. [_foundMedia insertObject:object atIndex:toIndexPath.item];
  846. [(MLFile *)object setFolderTrackNumber: @(toIndexPath.item - 1)];
  847. object = _foundMedia[fromIndexPath.item];
  848. if (![object isKindOfClass:[MLFile class]])
  849. return;
  850. [(MLFile *)object setFolderTrackNumber: @(fromIndexPath.item - 1)];
  851. }
  852. }
  853. - (void)collectionView:(UICollectionView *)collectionView requestToMoveItemAtIndexPath:(NSIndexPath *)itemPath intoFolderAtIndexPath:(NSIndexPath *)folderPath
  854. {
  855. id folderPathItem;
  856. id itemPathItem;
  857. @synchronized(_foundMedia) {
  858. folderPathItem = _foundMedia[folderPath.item];
  859. itemPathItem = _foundMedia[itemPath.item];
  860. }
  861. BOOL validFileTypeAtFolderPath = ([folderPathItem isKindOfClass:[MLFile class]] || [folderPathItem isKindOfClass:[MLLabel class]]) && [itemPathItem isKindOfClass:[MLFile class]];
  862. if (!validFileTypeAtFolderPath) {
  863. 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)]];
  864. alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
  865. [self updateViewContents];
  866. };
  867. [alert show];
  868. return;
  869. }
  870. BOOL isFolder = [folderPathItem isKindOfClass:[MLLabel class]];
  871. if (isFolder){
  872. MLLabel *folder = folderPathItem;
  873. MLFile *file = itemPathItem;
  874. [file setLabels:[[NSSet alloc] initWithObjects:folder, nil]];
  875. file.folderTrackNumber = @([folder.files count] - 1);
  876. @synchronized(_foundMedia) {
  877. [_foundMedia removeObjectAtIndex:itemPath.item];
  878. }
  879. [self updateViewContents];
  880. } else {
  881. _folderObject = folderPathItem;
  882. _indexPaths = [NSMutableArray arrayWithArray:@[itemPath]];
  883. [self showCreateFolderAlert];
  884. }
  885. }
  886. #pragma mark - collection/tableView helper
  887. - (NSArray *)selectedIndexPaths
  888. {
  889. NSArray *indexPaths;
  890. if (self.usingTableViewToShowData)
  891. indexPaths = [self.tableView indexPathsForSelectedRows];
  892. else
  893. indexPaths = [self.collectionView indexPathsForSelectedItems];
  894. return indexPaths ?: [NSArray array];
  895. }
  896. #pragma mark - Folder implementation
  897. - (void)rearrangeFolderTrackNumbersForRemovedItem:(MLFile *) mediaObject
  898. {
  899. MLLabel *label = [mediaObject.labels anyObject];
  900. NSSet *allFiles = label.files;
  901. for (MLFile *file in allFiles) {
  902. if (file.folderTrackNumber > mediaObject.folderTrackNumber) {
  903. int value = [file.folderTrackNumber intValue];
  904. file.folderTrackNumber = [NSNumber numberWithInt:value - 1];
  905. }
  906. }
  907. }
  908. - (void)showCreateFolderAlert
  909. {
  910. 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)]];
  911. [alert setAlertViewStyle:UIAlertViewStylePlainTextInput];
  912. UITextField *zeroTextField = [alert textFieldAtIndex:0];
  913. [zeroTextField setText:NSLocalizedString(@"FOLDER_NAME_PLACEHOLDER", nil)];
  914. [zeroTextField setClearButtonMode:UITextFieldViewModeAlways];
  915. __weak VLCAlertView *weakAlert = alert;
  916. alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
  917. if (cancelled)
  918. [self updateViewContents];
  919. else
  920. [self createFolderWithName:[weakAlert textFieldAtIndex:0].text];
  921. };
  922. [alert show];
  923. }
  924. - (void)createFolder
  925. {
  926. if (_libraryMode == VLCLibraryModeCreateFolder) {
  927. _libraryMode = _previousLibraryMode;
  928. [self updateViewContents];
  929. [self showCreateFolderAlert];
  930. return;
  931. }
  932. _indexPaths = [NSMutableArray arrayWithArray:[self selectedIndexPaths]];
  933. for (NSInteger i = _indexPaths.count - 1; i >=0; i--) {
  934. NSIndexPath *path = _indexPaths[i];
  935. id mediaObject;
  936. @synchronized(_foundMedia) {
  937. mediaObject = _foundMedia[path.row];
  938. }
  939. if ([mediaObject isKindOfClass:[MLLabel class]])
  940. [_indexPaths removeObject:path];
  941. }
  942. if ([_indexPaths count] != 0) {
  943. NSArray *folder = [MLLabel allLabels];
  944. //if we already have folders display them
  945. if ([folder count] > 0) {
  946. @synchronized(self) {
  947. _foundMedia = [NSMutableArray arrayWithArray:folder];
  948. }
  949. self.title = NSLocalizedString(@"SELECT_FOLDER", nil);
  950. _previousLibraryMode = _libraryMode;
  951. _libraryMode = VLCLibraryModeCreateFolder;
  952. [self reloadViews];
  953. return;
  954. }
  955. }
  956. //no selected items or no existing folder ask for foldername
  957. [self showCreateFolderAlert];
  958. }
  959. - (void)removeFromFolder
  960. {
  961. _indexPaths = [NSMutableArray arrayWithArray:[self selectedIndexPaths]];
  962. [_indexPaths sortUsingSelector:@selector(compare:)];
  963. for (NSInteger i = [_indexPaths count] - 1; i >= 0; i--) {
  964. NSIndexPath *path = _indexPaths[i];
  965. id item;
  966. @synchronized(_foundMedia) {
  967. item = _foundMedia[path.row];
  968. }
  969. if ([item isKindOfClass:[MLFile class]]) {
  970. MLFile *file = (MLFile *)item;
  971. [self rearrangeFolderTrackNumbersForRemovedItem:file];
  972. file.labels = nil;
  973. file.folderTrackNumber = nil;
  974. }
  975. @synchronized(_foundMedia) {
  976. [_foundMedia removeObject:item];
  977. }
  978. }
  979. [self reloadViews];
  980. }
  981. - (void)createFolderWithName:(NSString *)folderName
  982. {
  983. NSArray *labels = [MLLabel allLabels];
  984. for (MLLabel *label in labels){
  985. if ([label.name isEqualToString:folderName]) {
  986. _folderObject = nil;
  987. _indexPaths = nil;
  988. 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)]];
  989. alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
  990. [self updateViewContents];
  991. };
  992. [alert show];
  993. return;
  994. }
  995. }
  996. if (_folderObject != nil) {
  997. id mediaObject;
  998. NSUInteger folderIndex;
  999. @synchronized(_foundMedia) {
  1000. folderIndex = [_foundMedia indexOfObject:_folderObject];
  1001. mediaObject = _foundMedia[folderIndex];
  1002. }
  1003. //item got dragged onto item
  1004. if ([mediaObject isKindOfClass:[MLFile class]]) {
  1005. MLFile *file = (MLFile *)mediaObject;
  1006. MLLabel *label = [[MLMediaLibrary sharedMediaLibrary] createObjectForEntity:@"Label"];
  1007. label.name = folderName;
  1008. file.labels = [NSSet setWithObjects:label,nil];
  1009. NSNumber *folderTrackNumber = [NSNumber numberWithInt:(int)[label files].count - 1];
  1010. file.folderTrackNumber = folderTrackNumber;
  1011. id item;
  1012. @synchronized(_foundMedia) {
  1013. [_foundMedia removeObjectAtIndex:folderIndex];
  1014. [_foundMedia insertObject:label atIndex:folderIndex];
  1015. item = _foundMedia[((NSIndexPath *)_indexPaths[0]).item];
  1016. }
  1017. if (![item isKindOfClass:[MLFile class]])
  1018. return;
  1019. MLFile *itemFile = (MLFile *)item;
  1020. itemFile.labels = file.labels;
  1021. @synchronized(_foundMedia) {
  1022. [_foundMedia removeObjectAtIndex:((NSIndexPath *)_indexPaths[0]).item];
  1023. }
  1024. itemFile.folderTrackNumber = @([label files].count - 1);
  1025. } else {
  1026. //item got dragged onto folder or items should be added to folder
  1027. MLLabel *label;
  1028. @synchronized(_foundMedia) {
  1029. label = _foundMedia[folderIndex];
  1030. }
  1031. [_indexPaths sortUsingSelector:@selector(compare:)];
  1032. @synchronized(_foundMedia) {
  1033. NSUInteger count = _foundMedia.count;
  1034. for (NSInteger i = [_indexPaths count] - 1; i >= 0; i--) {
  1035. NSIndexPath *path = _indexPaths[i];
  1036. if (path.row >= count)
  1037. continue;
  1038. if (_libraryMode != VLCLibraryModeCreateFolder && ![_foundMedia[path.row] isKindOfClass:[MLFile class]])
  1039. continue;
  1040. if (_libraryMode == VLCLibraryModeCreateFolder)
  1041. [self updateViewContents];
  1042. id item = _foundMedia[path.row];
  1043. if (![item isKindOfClass:[MLFile class]])
  1044. continue;
  1045. MLFile *file = (MLFile *)item;
  1046. file.labels = [NSSet setWithObjects:label, nil];
  1047. [_foundMedia removeObjectAtIndex:path.row];
  1048. file.folderTrackNumber = @([label files].count - 1);
  1049. }
  1050. }
  1051. }
  1052. _folderObject = nil;
  1053. } else {
  1054. //create new folder
  1055. MLLabel *label = [[MLMediaLibrary sharedMediaLibrary] createObjectForEntity:@"Label"];
  1056. label.name = folderName;
  1057. //if items were selected
  1058. if ([_indexPaths count] != 0) {
  1059. [_indexPaths sortUsingSelector:@selector(compare:)];
  1060. for (NSInteger i = [_indexPaths count] - 1; i >= 0; i--) {
  1061. NSIndexPath *path = _indexPaths[i];
  1062. @synchronized(_foundMedia) {
  1063. NSUInteger index = self.usingTableViewToShowData ? path.row : path.item;
  1064. if (index < _foundMedia.count) {
  1065. id item = _foundMedia[index];
  1066. if (![item isKindOfClass:[MLFile class]])
  1067. continue;
  1068. MLFile *file = (MLFile *)item;
  1069. file.labels = [NSSet setWithObjects:label, nil];
  1070. file.folderTrackNumber = @([label files].count - 1);
  1071. [_foundMedia removeObjectAtIndex:index];
  1072. }
  1073. }
  1074. }
  1075. }
  1076. }
  1077. _indexPaths = nil;
  1078. [self setEditing:NO];
  1079. [self updateViewContents];
  1080. }
  1081. - (void)_collectionViewHandleLongPressGesture:(UIGestureRecognizer *) sender
  1082. {
  1083. if (sender.state == UIGestureRecognizerStateBegan && !self.isEditing)
  1084. [self setEditing:YES animated:YES];
  1085. }
  1086. #pragma mark - UI implementation
  1087. - (void)handleSelection
  1088. {
  1089. if (self.usingTableViewToShowData) {
  1090. NSInteger numberOfSections = [self.tableView numberOfSections];
  1091. for (NSInteger section = 0; section < numberOfSections; section++) {
  1092. NSInteger numberOfRowInSection = [self.tableView numberOfRowsInSection:section];
  1093. for (NSInteger row = 0; row < numberOfRowInSection; row++) {
  1094. if (!_isSelected)
  1095. [self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:section] animated:NO scrollPosition:UITableViewScrollPositionNone];
  1096. else
  1097. [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:section] animated:NO];
  1098. }
  1099. }
  1100. } else {
  1101. NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:0];
  1102. for (NSInteger item = 0; item < numberOfItems; item++) {
  1103. NSIndexPath *indexPath = [NSIndexPath indexPathForRow:item inSection:0];
  1104. if (!_isSelected)
  1105. [self.collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
  1106. else
  1107. [self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
  1108. [(VLCPlaylistCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath] selectionUpdate];
  1109. }
  1110. }
  1111. _isSelected = !_isSelected;
  1112. }
  1113. - (void)setEditing:(BOOL)editing animated:(BOOL)animated
  1114. {
  1115. [super setEditing:editing animated:animated];
  1116. _isSelected = NO;
  1117. UIBarButtonItem *editButton = self.editButtonItem;
  1118. editButton.tintColor = [UIColor whiteColor];
  1119. [self enableNavigationBarAnimation:!editing resetPositionWithAnimation:NO];
  1120. if (!editing && self.navigationItem.rightBarButtonItems.lastObject == _selectAllBarButtonItem)
  1121. [self.navigationItem setRightBarButtonItems: [self.navigationItem.rightBarButtonItems subarrayWithRange:NSMakeRange(0, self.navigationItem.rightBarButtonItems.count - 1)]];
  1122. else
  1123. [self.navigationItem setRightBarButtonItems:editing ? [self.navigationItem.rightBarButtonItems arrayByAddingObject:_selectAllBarButtonItem] : [self.navigationItem rightBarButtonItems] animated:YES];
  1124. if (self.usingTableViewToShowData) {
  1125. [self setSearchBar:!editing resetContent:!editing];
  1126. self.tableView.allowsMultipleSelectionDuringEditing = editing;
  1127. [self.tableView setEditing:editing animated:YES];
  1128. } else {
  1129. NSArray *visibleCells = self.collectionView.visibleCells;
  1130. [visibleCells enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  1131. VLCPlaylistCollectionViewCell *aCell = (VLCPlaylistCollectionViewCell*)obj;
  1132. // always clear selection when enter edit mode
  1133. aCell.selected = NO;
  1134. [aCell setEditing:editing animated:animated];
  1135. }];
  1136. self.collectionView.allowsMultipleSelection = editing;
  1137. /* UIKit doesn't clear the selection automagically if we leave the editing mode
  1138. * so we need to do so manually */
  1139. if (!editing) {
  1140. [self.collectionView addGestureRecognizer:_longPressGestureRecognizer];
  1141. NSArray *selectedItems = [self.collectionView indexPathsForSelectedItems];
  1142. NSUInteger count = selectedItems.count;
  1143. for (NSUInteger x = 0; x < count; x++)
  1144. [self.collectionView deselectItemAtIndexPath:selectedItems[x] animated:NO];
  1145. } else
  1146. [self.collectionView removeGestureRecognizer:_longPressGestureRecognizer];
  1147. }
  1148. if (_libraryMode == VLCLibraryModeCreateFolder) {
  1149. _libraryMode = _previousLibraryMode;
  1150. _indexPaths = nil;
  1151. [self updateViewContents];
  1152. }
  1153. self.navigationController.toolbarHidden = !editing;
  1154. [UIView performWithoutAnimation:^{
  1155. [editButton setTitle:editing ? NSLocalizedString(@"BUTTON_CANCEL", nil) : NSLocalizedString(@"BUTTON_EDIT", nil)];
  1156. }];
  1157. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  1158. NSMutableArray *rightBarButtonItems = [self.navigationItem.rightBarButtonItems mutableCopy];
  1159. UIBarButtonItem *toggleDisplayedView = rightBarButtonItems[0];
  1160. toggleDisplayedView.enabled = !editing;
  1161. rightBarButtonItems[0] = toggleDisplayedView;
  1162. self.navigationItem.rightBarButtonItems = rightBarButtonItems;
  1163. }
  1164. }
  1165. - (void)toggleDisplayedView:(UIBarButtonItem *)button
  1166. {
  1167. self.usingTableViewToShowData = !self.usingTableViewToShowData;
  1168. UIImage *newButtonImage = [UIImage imageNamed: self.usingTableViewToShowData ? @"collectionViewIcon" : @"tableViewIcon"];
  1169. [button setImage:newButtonImage];
  1170. [self updateViewsForCurrentDisplayMode];
  1171. }
  1172. - (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
  1173. {
  1174. return UITableViewCellEditingStyleDelete;
  1175. }
  1176. - (IBAction)leftButtonAction:(id)sender
  1177. {
  1178. [[VLCSidebarController sharedInstance] toggleSidebar];
  1179. if (self.isEditing)
  1180. [self setEditing:NO animated:YES];
  1181. }
  1182. - (IBAction)backToAllItems:(id)sender
  1183. {
  1184. _inFolder = NO;
  1185. UIBarButtonItem *createFolderItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemOrganize target:self action:@selector(createFolder)];
  1186. NSMutableArray *toolbarItems = [self.toolbarItems mutableCopy];
  1187. toolbarItems[2] = createFolderItem;
  1188. self.toolbarItems = toolbarItems;
  1189. [self setLibraryMode:_previousLibraryMode];
  1190. if (!self.isEditing) {
  1191. [self setSearchBar:YES resetContent:NO];
  1192. }
  1193. [self updateViewContents];
  1194. }
  1195. - (void)_endEditingWithHardReset:(BOOL)hardReset
  1196. {
  1197. [[MLMediaLibrary sharedMediaLibrary] updateMediaDatabase];
  1198. if (hardReset)
  1199. [self updateViewContents];
  1200. else
  1201. [self reloadViews];
  1202. [self setEditing:NO animated:YES];
  1203. }
  1204. - (void)deleteSelection:(id)sender
  1205. {
  1206. NSArray *indexPaths = [self usingTableViewToShowData] ? [self.tableView indexPathsForSelectedRows] : [self.collectionView indexPathsForSelectedItems];
  1207. if ((!indexPaths || [indexPaths count] == 0) && !_deleteFromTableView) {
  1208. UIAlertController *invalidSelection = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"DELETE_INVALID_TITLE", nil) message:NSLocalizedString(@"DELETE_INVALID_MESSAGE", nil) preferredStyle:UIAlertControllerStyleAlert];
  1209. UIAlertAction *doneAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_OK", nil) style:UIAlertActionStyleDefault handler:nil];
  1210. [invalidSelection addAction:doneAction];
  1211. [self presentViewController:invalidSelection animated:YES completion:nil];
  1212. } else {
  1213. UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
  1214. UIAlertAction *deleteAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_DELETE", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
  1215. if (_deleteFromTableView) {
  1216. @synchronized (_foundMedia) {
  1217. NSIndexPath *indexPath = (NSIndexPath *)sender;
  1218. if (indexPath && indexPath.row < _foundMedia.count)
  1219. [self removeMediaObject: _foundMedia[indexPath.row] updateDatabase:YES];
  1220. }
  1221. } else
  1222. [self deletionAfterConfirmation];
  1223. _deleteFromTableView = NO;
  1224. }];
  1225. UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_CANCEL", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
  1226. _deleteFromTableView ? [self setEditing:NO animated:YES] : [self reloadViews];
  1227. _deleteFromTableView = NO;
  1228. }];
  1229. [alert addAction:deleteAction];
  1230. [alert addAction:cancelAction];
  1231. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  1232. CGRect tmpBounds = self.view.bounds;
  1233. [alert setTitle:NSLocalizedString(@"DELETE_TITLE", nil)];
  1234. [alert setMessage:NSLocalizedString(@"DELETE_MESSAGE", nil)];
  1235. [alert.popoverPresentationController setPermittedArrowDirections:0];
  1236. [alert.popoverPresentationController setSourceView:self.view];
  1237. [alert.popoverPresentationController setSourceRect:CGRectMake(tmpBounds.size.width / 2.0, tmpBounds.size.height / 2.0, 1.0, 1.0)];
  1238. }
  1239. [self presentViewController:alert animated:YES completion:nil];
  1240. }
  1241. }
  1242. - (void)deletionAfterConfirmation
  1243. {
  1244. NSArray *indexPaths;
  1245. if (self.usingTableViewToShowData)
  1246. indexPaths = [self.tableView indexPathsForSelectedRows];
  1247. else
  1248. indexPaths = [self.collectionView indexPathsForSelectedItems];
  1249. @synchronized(_foundMedia) {
  1250. NSUInteger count = indexPaths.count;
  1251. NSMutableArray *objects = [[NSMutableArray alloc] initWithCapacity:count];
  1252. for (NSUInteger x = 0; x < count; x++)
  1253. [objects addObject:_foundMedia[[indexPaths[x] row]]];
  1254. for (NSUInteger x = 0; x < count; x++)
  1255. [self removeMediaObject:objects[x] updateDatabase:NO];
  1256. }
  1257. [self _endEditingWithHardReset:YES];
  1258. }
  1259. - (void)renameSelection
  1260. {
  1261. NSArray *indexPaths = [self selectedIndexPaths];
  1262. if (indexPaths.count < 1) {
  1263. [self _endEditingWithHardReset:NO];
  1264. return;
  1265. }
  1266. NSString *itemName;
  1267. if (self.usingTableViewToShowData)
  1268. itemName = [(VLCPlaylistTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPaths[0]] titleLabel].text;
  1269. else
  1270. itemName = [(VLCPlaylistCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPaths[0]] titleLabel].text;
  1271. 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)]];
  1272. [alert setAlertViewStyle:UIAlertViewStylePlainTextInput];
  1273. [[alert textFieldAtIndex:0] setText:itemName];
  1274. [[alert textFieldAtIndex:0] setClearButtonMode:UITextFieldViewModeAlways];
  1275. __weak VLCAlertView *weakAlert = alert;
  1276. alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
  1277. if (cancelled)
  1278. [self _endEditingWithHardReset:NO];
  1279. else
  1280. [self renameMediaObjectTo:[weakAlert textFieldAtIndex:0].text];
  1281. };
  1282. [alert show];
  1283. }
  1284. - (void)renameMediaObjectTo:(NSString*)newName
  1285. {
  1286. NSArray *indexPaths = [self selectedIndexPaths];
  1287. if (indexPaths.count < 1)
  1288. return;
  1289. id mediaObject;
  1290. @synchronized (_foundMedia) {
  1291. NSUInteger row = [indexPaths[0] row];
  1292. if (row >= _foundMedia.count)
  1293. return;
  1294. mediaObject = _foundMedia[row];
  1295. }
  1296. if ([mediaObject isKindOfClass:[MLAlbum class]] || [mediaObject isKindOfClass:[MLShowEpisode class]] || [mediaObject isKindOfClass:[MLShow class]] || [mediaObject isKindOfClass:[MLLabel class]] )
  1297. [mediaObject setName:newName];
  1298. else
  1299. [mediaObject setTitle:newName];
  1300. if (self.usingTableViewToShowData)
  1301. [self.tableView deselectRowAtIndexPath:indexPaths[0] animated:YES];
  1302. else
  1303. [self.collectionView deselectItemAtIndexPath:indexPaths[0] animated:YES];
  1304. if (indexPaths.count > 1)
  1305. [self renameSelection];
  1306. else
  1307. [self _endEditingWithHardReset:NO];
  1308. }
  1309. - (void)updateViewsForCurrentDisplayMode
  1310. {
  1311. UIImage *newButtonImage = [UIImage imageNamed: self.usingTableViewToShowData ? @"collectionViewIcon" : @"tableViewIcon"];
  1312. [self.displayModeBarButtonItem setImage:newButtonImage];
  1313. }
  1314. #pragma mark - Sharing
  1315. // 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.
  1316. - (void)updateActionBarButtonItemStateWithSelectedIndexPaths:(NSArray *)indexPaths
  1317. {
  1318. NSUInteger count = [indexPaths count];
  1319. if (!indexPaths || count == 0) {
  1320. _openInActivityBarButtonItem.enabled = NO;
  1321. } else {
  1322. // Look for at least one MLFile
  1323. @synchronized(_foundMedia) {
  1324. for (NSUInteger x = 0; x < count; x++) {
  1325. id mediaItem = _foundMedia[[indexPaths[x] row]];
  1326. if ([mediaItem isKindOfClass:[MLFile class]] || [mediaItem isKindOfClass:[MLAlbumTrack class]] | [mediaItem isKindOfClass:[MLShowEpisode class]]) {
  1327. _openInActivityBarButtonItem.enabled = YES;
  1328. return;
  1329. }
  1330. }
  1331. }
  1332. }
  1333. }
  1334. - (void)actOnSelection:(UIBarButtonItem *)barButtonItem
  1335. {
  1336. NSParameterAssert(barButtonItem);
  1337. if (!barButtonItem) {
  1338. APLog(@"Missing a UIBarButtonItem to present from");
  1339. return;
  1340. }
  1341. NSArray *indexPaths = [self selectedIndexPaths];
  1342. NSUInteger count = indexPaths.count;
  1343. if (count) {
  1344. NSMutableArray /* NSURL */ *fileURLobjects = [[NSMutableArray alloc] initWithCapacity:count];
  1345. for (NSUInteger x = 0; x < count; x++) {
  1346. id mediaItem;
  1347. @synchronized (_foundMedia) {
  1348. mediaItem = _foundMedia[[indexPaths[x] row]];
  1349. }
  1350. NSURL *fileURL;
  1351. if ([mediaItem isKindOfClass:[MLFile class]])
  1352. fileURL = [(MLFile *) mediaItem url];
  1353. else if ([mediaItem isKindOfClass:[MLAlbumTrack class]])
  1354. fileURL = [[(MLAlbumTrack *) mediaItem anyFileFromTrack] url];
  1355. else if ([mediaItem isKindOfClass:[MLShowEpisode class]])
  1356. fileURL = [[(MLShowEpisode *) mediaItem anyFileFromEpisode] url];
  1357. if ([fileURL isFileURL])
  1358. [fileURLobjects addObject:fileURL];
  1359. }
  1360. if ([fileURLobjects count]) {
  1361. // 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)
  1362. [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
  1363. // 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.
  1364. dispatch_async(dispatch_get_main_queue(), ^{
  1365. _openInActivity = [[VLCOpenInActivity alloc] init];
  1366. _openInActivity.presentingViewController = self;
  1367. _openInActivity.presentingBarButtonItem = barButtonItem;
  1368. dispatch_block_t enableInteractionBlock = ^{
  1369. // Strangely makeObjectsPerformSelector:withObject has trouble here (when called from presentViewController:animated:completion:)
  1370. // [self.navigationController.toolbar.items makeObjectsPerformSelector:@selector(setEnabled:) withObject:@(YES)];
  1371. for (UIBarButtonItem *item in self.navigationController.toolbar.items) {
  1372. item.enabled = YES;
  1373. }
  1374. if ([[UIApplication sharedApplication] isIgnoringInteractionEvents]) {
  1375. [[UIApplication sharedApplication] endIgnoringInteractionEvents];
  1376. }
  1377. };
  1378. UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:fileURLobjects applicationActivities:@[_openInActivity]];
  1379. if (SYSTEM_RUNS_IOS8_OR_LATER)
  1380. controller.popoverPresentationController.sourceView = self.navigationController.toolbar;
  1381. controller.completionHandler = ^(NSString *activityType, BOOL completed) {
  1382. APLog(@"UIActivityViewController finished with activity type: %@, completed: %i", activityType, completed);
  1383. // 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.
  1384. // 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.
  1385. if (completed && [activityType isEqualToString:UIActivityTypeSaveToCameraRoll] && [ALAssetsLibrary authorizationStatus] == ALAuthorizationStatusAuthorized) {
  1386. VLCAlertView *alertView = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"SHARING_SUCCESS_CAMERA_ROLL", nil)
  1387. message:nil
  1388. delegate:nil
  1389. cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
  1390. otherButtonTitles:nil];
  1391. [alertView show];
  1392. }
  1393. _openInActivity = nil;
  1394. // 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.
  1395. // enableInteractionBlock();
  1396. };
  1397. [self.navigationController presentViewController:controller animated:YES completion:enableInteractionBlock];
  1398. });
  1399. return;
  1400. }
  1401. }
  1402. VLCAlertView *alertView = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"SHARING_ERROR_NO_FILES", nil)
  1403. message:nil
  1404. delegate:nil
  1405. cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
  1406. otherButtonTitles:nil];
  1407. [alertView show];
  1408. }
  1409. #pragma mark - properties
  1410. - (void)setLibraryMode:(VLCLibraryMode)mode
  1411. {
  1412. _libraryMode = mode;
  1413. [self updateViewContents];
  1414. if (mode == VLCLibraryModeAllAlbums ||
  1415. mode == VLCLibraryModeAllSeries ||
  1416. mode == VLCLibraryModeAllFiles) {
  1417. _previousLibraryMode = mode;
  1418. }
  1419. }
  1420. - (BOOL)usingTableViewToShowData
  1421. {
  1422. return [[NSUserDefaults standardUserDefaults] boolForKey:kUsingTableViewToShowData];
  1423. }
  1424. - (void)setUsingTableViewToShowData:(BOOL)usingTableViewToShowData
  1425. {
  1426. [[NSUserDefaults standardUserDefaults] setBool:usingTableViewToShowData forKey:kUsingTableViewToShowData];
  1427. [self updateViewsForCurrentDisplayMode];
  1428. [self setupContentViewWithContentInset:usingTableViewToShowData];
  1429. }
  1430. #pragma mark - autorotation
  1431. // RootController is responsible for supporting interface orientation(iOS6.0+), i.e. navigation controller
  1432. // so this will not work as intended without "voodoo magic"(UINavigationController category, subclassing, etc)
  1433. /* introduced in iOS 6 */
  1434. - (UIInterfaceOrientationMask)supportedInterfaceOrientations
  1435. {
  1436. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
  1437. return UIInterfaceOrientationMaskAll;
  1438. return (_foundMedia.count > 0)? UIInterfaceOrientationMaskAllButUpsideDown:
  1439. UIInterfaceOrientationMaskPortrait;
  1440. }
  1441. /* introduced in iOS 6 */
  1442. - (BOOL)shouldAutorotate
  1443. {
  1444. return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || (_foundMedia.count > 0);
  1445. }
  1446. - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
  1447. {
  1448. [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
  1449. [self setViewFromDeviceOrientation];
  1450. if (self.usingTableViewToShowData) {
  1451. NSArray *visibleCells = [self.tableView visibleCells];
  1452. NSUInteger cellCount = visibleCells.count;
  1453. for (NSUInteger x = 0; x < cellCount; x++) {
  1454. if ([visibleCells[x] isExpanded])
  1455. [visibleCells[x] metaDataLabel].hidden = YES;
  1456. }
  1457. } else {
  1458. [self.collectionView.collectionViewLayout invalidateLayout];
  1459. }
  1460. }
  1461. #pragma mark - Search Display Controller Delegate
  1462. - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
  1463. {
  1464. @synchronized (_searchData) {
  1465. [_searchData removeAllObjects];
  1466. }
  1467. NSManagedObject *item;
  1468. NSRange nameRange;
  1469. @synchronized(_foundMedia) {
  1470. NSInteger listCount = _foundMedia.count;
  1471. for (int i = 0; i < listCount; i++) {
  1472. item = _foundMedia[i];
  1473. if ([item isKindOfClass:[MLAlbum class]]) {
  1474. nameRange = [self _searchAlbum:(MLAlbum *)item forString:searchString];
  1475. } else if ([item isKindOfClass:[MLAlbumTrack class]]) {
  1476. nameRange = [self _searchAlbumTrack:(MLAlbumTrack *)item forString:searchString];
  1477. } else if ([item isKindOfClass:[MLShowEpisode class]]) {
  1478. nameRange = [self _searchShowEpisode:(MLShowEpisode *)item forString:searchString];
  1479. } else if ([item isKindOfClass:[MLShow class]]) {
  1480. nameRange = [self _searchShow:(MLShow *)item forString:searchString];
  1481. } else if ([item isKindOfClass:[MLLabel class]])
  1482. nameRange = [self _searchLabel:(MLLabel *)item forString:searchString];
  1483. else // simple file
  1484. nameRange = [self _searchFile:(MLFile*)item forString:searchString];
  1485. @synchronized (_searchData) {
  1486. if (nameRange.location != NSNotFound)
  1487. [_searchData addObject:item];
  1488. }
  1489. }
  1490. }
  1491. return YES;
  1492. }
  1493. - (NSRange)_searchAlbumTrack:(MLAlbumTrack *)albumTrack forString:(NSString *)searchString
  1494. {
  1495. NSString *trackTitle = albumTrack.title;
  1496. NSRange nameRange = [trackTitle rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1497. if (nameRange.location != NSNotFound)
  1498. return nameRange;
  1499. NSMutableArray *stringsToSearch = [[NSMutableArray alloc] initWithObjects:trackTitle, nil];
  1500. if ([albumTrack artist])
  1501. [stringsToSearch addObject:[albumTrack artist]];
  1502. if ([albumTrack genre])
  1503. [stringsToSearch addObject:[albumTrack genre]];
  1504. NSArray *substrings = [searchString componentsSeparatedByString:@" "];
  1505. NSUInteger substringCount = substrings.count;
  1506. NSUInteger searchStringCount = stringsToSearch.count;
  1507. for (NSUInteger x = 0; x < substringCount; x++) {
  1508. for (NSUInteger y = 0; y < searchStringCount; y++) {
  1509. nameRange = [stringsToSearch[y] rangeOfString:substrings[x] options:NSCaseInsensitiveSearch];
  1510. if (nameRange.location != NSNotFound)
  1511. break;
  1512. }
  1513. if (nameRange.location != NSNotFound)
  1514. break;
  1515. }
  1516. return nameRange;
  1517. }
  1518. - (NSRange)_searchAlbum:(MLAlbum *)album forString:(NSString *)searchString
  1519. {
  1520. NSString *albumName = [album name];
  1521. NSRange nameRange = [albumName rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1522. if (nameRange.location != NSNotFound)
  1523. return nameRange;
  1524. if ([album releaseYear]) {
  1525. nameRange = [[album releaseYear] rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1526. if (nameRange.location != NSNotFound)
  1527. return nameRange;
  1528. }
  1529. /* split search string into substrings and try again */
  1530. NSArray *substrings = [searchString componentsSeparatedByString:@" "];
  1531. NSUInteger substringCount = substrings.count;
  1532. if (substringCount > 1) {
  1533. for (NSUInteger x = 0; x < substringCount; x++) {
  1534. nameRange = [searchString rangeOfString:substrings[x] options:NSCaseInsensitiveSearch];
  1535. if (nameRange.location != NSNotFound)
  1536. break;
  1537. }
  1538. }
  1539. if (nameRange.location != NSNotFound)
  1540. return nameRange;
  1541. /* search our tracks if we can't find what the user is looking for */
  1542. NSArray *tracks = [album sortedTracks];
  1543. NSUInteger trackCount = tracks.count;
  1544. for (NSUInteger x = 0; x < trackCount; x++) {
  1545. nameRange = [self _searchAlbumTrack:tracks[x] forString:searchString];
  1546. if (nameRange.location != NSNotFound)
  1547. break;
  1548. }
  1549. return nameRange;
  1550. }
  1551. - (NSRange)_searchShowEpisode:(MLShowEpisode *)episode forString:(NSString *)searchString
  1552. {
  1553. /* basic search first, then try more complex things */
  1554. NSString *episodeName = [episode name];
  1555. NSRange nameRange;
  1556. if (episodeName) {
  1557. nameRange = [episodeName rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1558. if (nameRange.location != NSNotFound)
  1559. return nameRange;
  1560. }
  1561. /* split search string into substrings and try again */
  1562. NSArray *substrings = [searchString componentsSeparatedByString:@" "];
  1563. NSUInteger substringCount = substrings.count;
  1564. if (substringCount > 1) {
  1565. for (NSUInteger x = 0; x < substringCount; x++) {
  1566. nameRange = [searchString rangeOfString:substrings[x] options:NSCaseInsensitiveSearch];
  1567. if (nameRange.location != NSNotFound)
  1568. break;
  1569. }
  1570. }
  1571. return nameRange;
  1572. }
  1573. - (NSRange)_searchShow:(MLShow *)mediaShow forString:(NSString *)searchString
  1574. {
  1575. /* basic search first, then try more complex things */
  1576. NSRange nameRange = [[mediaShow name] rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1577. if (nameRange.location != NSNotFound)
  1578. return nameRange;
  1579. /* split search string into substrings and try again */
  1580. NSArray *substrings = [searchString componentsSeparatedByString:@" "];
  1581. NSUInteger substringCount = substrings.count;
  1582. if (substringCount > 1) {
  1583. for (NSUInteger x = 0; x < substringCount; x++) {
  1584. nameRange = [searchString rangeOfString:substrings[x] options:NSCaseInsensitiveSearch];
  1585. if (nameRange.location != NSNotFound)
  1586. break;
  1587. }
  1588. }
  1589. if (nameRange.location != NSNotFound)
  1590. return nameRange;
  1591. /* user didn't search for our show name, let's do a deeper search on the episodes */
  1592. NSArray *episodes = [mediaShow sortedEpisodes];
  1593. NSUInteger episodeCount = episodes.count;
  1594. for (NSUInteger x = 0; x < episodeCount; x++)
  1595. nameRange = [self _searchShowEpisode:episodes[x] forString:searchString];
  1596. return nameRange;
  1597. }
  1598. - (NSRange)_searchLabel:(MLLabel *)mediaLabel forString:(NSString *)searchString
  1599. {
  1600. /* basic search first, then try more complex things */
  1601. NSRange nameRange = [[mediaLabel name] rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1602. if (nameRange.location != NSNotFound)
  1603. return nameRange;
  1604. /* user didn't search for our label name, let's do a deeper search */
  1605. NSArray *files = [mediaLabel sortedFolderItems];
  1606. NSUInteger fileCount = files.count;
  1607. for (NSUInteger x = 0; x < fileCount; x++) {
  1608. nameRange = [self _searchFile:files[x] forString:searchString];
  1609. if (nameRange.location != NSNotFound)
  1610. break;
  1611. }
  1612. return nameRange;
  1613. }
  1614. - (NSRange)_searchFile:(MLFile *)mediaFile forString:(NSString *)searchString
  1615. {
  1616. /* basic search first, then try more complex things */
  1617. NSRange nameRange = [[mediaFile title] rangeOfString:searchString options:NSCaseInsensitiveSearch];
  1618. if (nameRange.location != NSNotFound)
  1619. return nameRange;
  1620. NSMutableArray *stringsToSearch = [[NSMutableArray alloc] initWithObjects:[mediaFile title], nil];
  1621. if ([mediaFile artist])
  1622. [stringsToSearch addObject:[mediaFile artist]];
  1623. if ([mediaFile releaseYear])
  1624. [stringsToSearch addObject:[mediaFile releaseYear]];
  1625. NSArray *substrings = [searchString componentsSeparatedByString:@" "];
  1626. NSUInteger substringCount = substrings.count;
  1627. NSUInteger searchStringCount = stringsToSearch.count;
  1628. for (NSUInteger x = 0; x < substringCount; x++) {
  1629. for (NSUInteger y = 0; y < searchStringCount; y++) {
  1630. nameRange = [stringsToSearch[y] rangeOfString:substrings[x] options:NSCaseInsensitiveSearch];
  1631. if (nameRange.location != NSNotFound)
  1632. break;
  1633. }
  1634. if (nameRange.location != NSNotFound)
  1635. break;
  1636. }
  1637. return nameRange;
  1638. }
  1639. #pragma mark - handoff
  1640. - (void)restoreUserActivityState:(NSUserActivity *)activity
  1641. {
  1642. NSString *userActivityType = activity.activityType;
  1643. if([userActivityType isEqualToString:kVLCUserActivityLibraryMode] ||
  1644. [userActivityType isEqualToString:kVLCUserActivityLibrarySelection]) {
  1645. NSDictionary *dict = activity.userInfo;
  1646. NSString *folderPath = dict[@"folder"];
  1647. if (!folderPath) return;
  1648. NSURL *folderURL = [NSURL URLWithString:folderPath];
  1649. @synchronized(_foundMedia) {
  1650. NSUInteger count = _foundMedia.count;
  1651. for (NSUInteger i = 0; i < count; i++) {
  1652. NSManagedObject *object = _foundMedia[i];
  1653. if([object.objectID.URIRepresentation isEqual:folderURL]) {
  1654. [self tableView:self.tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
  1655. }
  1656. }
  1657. }
  1658. }
  1659. }
  1660. - (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView
  1661. {
  1662. tableView.rowHeight = 90.;
  1663. tableView.backgroundColor = [UIColor blackColor];
  1664. }
  1665. @end