Repository: danylokostyshyn/popcorntime-ios Branch: master Commit: 555aff8d10e3 Files: 66 Total size: 352.1 KB Directory structure: gitextract_6kv41eo_/ ├── .gitignore ├── Podfile ├── PopcornTime/ │ ├── Controllers/ │ │ ├── AnimeDetailsViewController.swift │ │ ├── AnimeViewController.swift │ │ ├── BarHidingViewController.swift │ │ ├── BaseCollectionViewController.swift │ │ ├── BaseDetailsViewController.swift │ │ ├── ColorfullTabBarController.swift │ │ ├── FavoritesViewController.swift │ │ ├── LoadingViewController.swift │ │ ├── MovieDetailsViewController.swift │ │ ├── MoviesViewController.swift │ │ ├── OAuthViewController.swift │ │ ├── PagedViewController.swift │ │ ├── ParseViewController.swift │ │ ├── SettingsViewController.swift │ │ ├── ShowDetailsViewController.swift │ │ └── ShowsViewController.swift │ ├── Info.plist │ ├── Models/ │ │ ├── APIManager.swift │ │ ├── Anime.swift │ │ ├── AppDelegate.swift │ │ ├── BaseStructures.swift │ │ ├── BasicInfo.swift │ │ ├── DataManager.swift │ │ ├── Extensions.swift │ │ ├── Image.swift │ │ ├── ImageProvider.swift │ │ ├── Movie.swift │ │ ├── PTAPIManager.h │ │ ├── PTAPIManager.m │ │ ├── PTTorrentStreamer.h │ │ ├── PTTorrentStreamer.mm │ │ ├── ParseManager.swift │ │ └── Show.swift │ ├── PopcornTime-Bridging-Header.h │ ├── Resources/ │ │ ├── Images.xcassets/ │ │ │ ├── AnimeIcon.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── BigLogo.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── SubwayIconSet/ │ │ │ ├── AddToFavoritesIcon.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── FavoritesIcon.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── MoviesIcon.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── RemoveFromFavoritesIcon.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── SettingsIcon.imageset/ │ │ │ │ └── Contents.json │ │ │ └── ShowsIcon.imageset/ │ │ │ └── Contents.json │ │ ├── Launch Screen.xib │ │ └── Main.storyboard │ └── Views/ │ ├── EpisodeCell.swift │ ├── EpisodeCell.xib │ ├── MoreShowsCollectionViewCell.swift │ ├── MoreShowsCollectionViewCell.xib │ ├── SeasonHeader.swift │ ├── SeasonHeader.xib │ ├── ShowCollectionViewCell.swift │ ├── ShowCollectionViewCell.xib │ ├── StratchyHeader.swift │ ├── StratchyHeader.xib │ └── StratchyHeaderLayout.swift ├── PopcornTime.xcodeproj/ │ ├── project.pbxproj │ └── xcshareddata/ │ └── xcschemes/ │ └── PopcornTime.xcscheme ├── README.md ├── Thirdparties/ │ └── VLCKit/ │ └── Dropin-Player/ │ ├── VDLPlaybackViewController.h │ ├── VDLPlaybackViewController.m │ └── VDLPlaybackViewController.xib └── popcorntime_api.paw ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Created by https://www.gitignore.io ### OSX ### .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Xcode ### build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.xcuserstate ### Objective-C ### # Xcode # build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.hmap *.ipa *.xcuserstate # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control # *.xcworkspace/ Pods/ ================================================ FILE: Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '8.0' inhibit_all_warnings! source 'https://github.com/danylokostyshyn/private-podspecs.git' source 'https://github.com/CocoaPods/Specs' target 'PopcornTime' do pod 'private-MobileVLCKit' pod 'private-boost' pod 'private-libtorrent' pod 'private-openssl' pod 'Reachability' pod 'CocoaSecurity' pod 'SDWebImage' pod 'Crashlytics' pod 'Fabric' end ================================================ FILE: PopcornTime/Controllers/AnimeDetailsViewController.swift ================================================ // // ShowDetailsViewController.swift // PopcornTime // // Created by Andrew K. on 3/13/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class AnimeDetailsViewController: BaseDetailsViewController { var anime: Anime! { get { return self.item as! Anime } } // MARK: - UIViewController override func viewDidLoad() { super.viewDidLoad() preferedOtherHeadersHeight = 0.0 } // MARK: - BaseDetailsViewController override func reloadData() { PTAPIManager.shared().showInfo(with: .anime, withId: item.identifier, success: { (item) -> Void in guard let item = item else { return } self.anime.update(item) self.collectionView?.reloadData() }, failure: nil) } // MARK: - DetailViewControllerDataSource override func numberOfSeasons() -> Int { return anime.seasons.count } override func numberOfEpisodesInSeason(_ seasonsIndex: Int) -> Int { return anime.seasons[seasonsIndex].episodes.count } override func setupCell(_ cell: EpisodeCell, seasonIndex: Int, episodeIndex: Int) { let episode = anime.seasons[seasonIndex].episodes[episodeIndex] cell.titleLabel.text = "\(episode.episodeNumber)" } override func setupSeasonHeader(_ header: SeasonHeader, seasonIndex: Int) { } override func cellWasPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) { let episode = anime.seasons[seasonIndex].episodes[episodeIndex] showVideoPickerPopupForEpisode(episode, basicInfo: self.item, fromView: cell) } override func cellWasLongPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) { // let episode = anime.episodeFor(seasonIndex: seasonIndex, episodeIndex: episodeIndex) // let seasonEpisodes = anime.episodesFor(seasonIndex: seasonIndex) } } ================================================ FILE: PopcornTime/Controllers/AnimeViewController.swift ================================================ // // MoviesViewController.swift // PopcornTime // // Created by Andrew K. on 3/15/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class AnimeViewController: PagedViewController { override var showType: PTItemType { get { return .anime } } override func map(_ response: [AnyObject]) -> [BasicInfo] { return response.map({ Anime(dictionary: $0 as! [AnyHashable: Any]) }) } func collectionView(_ collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: IndexPath) { if let cell = collectionView.cellForItem(at: indexPath){ //Check if cell is MoreShowsCell if let _ = cell as? MoreShowsCollectionViewCell{ loadMore() } else { performSegue(withIdentifier: "showDetails", sender: cell) } } } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { super.prepare(for: segue, sender: sender) if segue.identifier == "showDetails"{ if let episodesVC = segue.destination as? AnimeDetailsViewController{ if let senderCell = sender as? UICollectionViewCell{ if let indexPath = collectionView!.indexPath(for: senderCell){ var item: BasicInfo! if (searchController!.isActive) { item = searchResults[indexPath.row] } else { item = items[indexPath.row] } episodesVC.item = item as! Anime } } } } } } ================================================ FILE: PopcornTime/Controllers/BarHidingViewController.swift ================================================ // // BarHidingViewController.swift // PopcornTime // // Created by Andrew K. on 3/13/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class BarHidingViewController: UIViewController { fileprivate var barsVisible:Bool = true { willSet { self.tabBarController?.tabBar.isHidden = !newValue self.navigationController?.navigationBar.isHidden = !newValue UIApplication.shared.setStatusBarHidden(!newValue, with: .fade) } } override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() //Hide bars if needed let sizeClass = (horizontal: self.view.traitCollection.horizontalSizeClass, vertical: self.view.traitCollection.verticalSizeClass) switch sizeClass{ case (_,.compact): self.barsVisible = false default: self.barsVisible = true } } } ================================================ FILE: PopcornTime/Controllers/BaseCollectionViewController.swift ================================================ // // ShowsCollectionViewController.swift // PopcornTime // // Created by Andrew K. on 3/8/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit let reuseIdentifierShow = "ShowCell" let reuseIdentifierMore = "MoreShowsCell" ///Base class for displaying collection of shows, subclass MUST override reloadData() and set self.shows in it class BaseCollectionViewController: BarHidingViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate { fileprivate struct Constants{ static let desirediPadCellWidth = 160 static let desirediPadCellHeight = 205 static let numberOfLinesiPhonePortrait = 2 static let numberOfItemsiPhonePortrait = 2 static let numberOfLinesiPhoneLandscape = 2 static let numberOfItemsiPhoneLandscape = 5 } var items = [BasicInfo]() var showLoadMoreCell = false @IBOutlet weak var collectionView: UICollectionView!{ didSet{ collectionView.alwaysBounceVertical = true collectionView.dataSource = self collectionView.delegate = self } } @IBOutlet weak var collectionViewLayout: UICollectionViewFlowLayout! // MARK: UIViewController override func viewDidLoad() { super.viewDidLoad() self.collectionView!.register(UINib(nibName: "ShowCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: reuseIdentifierShow) self.collectionView!.register(UINib(nibName: "MoreShowsCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: reuseIdentifierMore) self.collectionView?.delegate = self self.collectionView?.collectionViewLayout.invalidateLayout() self.reloadData() } // MARK: UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { let additionalCellsCount = self.showLoadMoreCell ? 1 : 0 return (items.count + additionalCellsCount) } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { if (self.showLoadMoreCell && indexPath.row == items.count) { //Last cell let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifierMore, for: indexPath) as! MoreShowsCollectionViewCell return cell } else { //Ordinary show cell let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifierShow, for: indexPath) as! ShowCollectionViewCell let item = items[indexPath.row] cell.title = item.title let imageItem = item.smallImage switch imageItem?.status { case .new?: imageItem?.status = .downloading ImageProvider.sharedInstance.imageFromURL(URL: imageItem?.URL) { (downloadedImage) -> () in imageItem?.image = downloadedImage imageItem?.status = .finished collectionView.reloadItems(at: [indexPath]) } case .finished?: cell.image = imageItem?.image default: break } return cell } } // MARK: UICollectionViewDelegateFlowLayout & UICollectionViewDelegate func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let visibleAreaHeight = collectionView.bounds.height - navigationController!.navigationBar.bounds.height - UIApplication.shared.statusBarFrame.height - self.tabBarController!.tabBar.bounds.height let visibleAreaWidth = collectionView.bounds.width //Set cell size based on size class. let sizeClass = (horizontal: self.view.traitCollection.horizontalSizeClass, vertical: self.view.traitCollection.verticalSizeClass) if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout{ switch sizeClass{ case (.compact,.regular): //iPhone portrait let cellWidth = ((visibleAreaWidth - CGFloat(Constants.numberOfItemsiPhonePortrait - 1)*flowLayout.minimumInteritemSpacing - flowLayout.sectionInset.top - flowLayout.sectionInset.bottom)/CGFloat(Constants.numberOfItemsiPhonePortrait)) let cellHeight = ((visibleAreaHeight - CGFloat(Constants.numberOfLinesiPhonePortrait - 1)*flowLayout.minimumLineSpacing - flowLayout.sectionInset.left - flowLayout.sectionInset.right)/CGFloat(Constants.numberOfLinesiPhonePortrait)) return CGSize(width: cellWidth, height: cellHeight) case (_,.compact): //iPhone landscape let cellWidth = ((collectionView.bounds.width - CGFloat(Constants.numberOfItemsiPhoneLandscape - 1)*flowLayout.minimumInteritemSpacing - flowLayout.sectionInset.top - flowLayout.sectionInset.bottom)/CGFloat(Constants.numberOfItemsiPhoneLandscape)) let cellHeight = ((collectionView.bounds.height - CGFloat(Constants.numberOfLinesiPhoneLandscape - 1)*flowLayout.minimumLineSpacing - flowLayout.sectionInset.left - flowLayout.sectionInset.right)/CGFloat(Constants.numberOfLinesiPhoneLandscape)) return CGSize(width: cellWidth, height: cellHeight) case (_,_): // iPad. Calculate cell size based on desired size let numberOfLines = Int(visibleAreaHeight) / Constants.desirediPadCellHeight let betweenLinesSpaceSum = CGFloat(numberOfLines - 1) * flowLayout.minimumLineSpacing let sectionInsetsVerticalSum = flowLayout.sectionInset.top + flowLayout.sectionInset.bottom let adjustedHeight = (visibleAreaHeight - betweenLinesSpaceSum - sectionInsetsVerticalSum)/CGFloat(numberOfLines) let adjustedWidth = adjustedHeight * CGFloat(Constants.desirediPadCellWidth) / CGFloat(Constants.desirediPadCellHeight) return CGSize(width: adjustedWidth, height: adjustedHeight) } } return CGSize(width: 50, height: 50) } // MARK: - ShowsCollectionViewController func reloadData(){ assert(true, "Sublcass MUST override this method") } } ================================================ FILE: PopcornTime/Controllers/BaseDetailsViewController.swift ================================================ // // BaseDetailsViewController.swift // PopcornTime // // Created by Danylo Kostyshyn on 3/21/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit /// With this protocol we encapsulate calls of collectionView indexPathes. For now we have one extra section at the top (empty one with stratchy header), this way if anything changes here we will change all logic here, and all users of this protocol will not have to hcange anything. So it's a good idea to use seasonIndex, episodeIndex instead of indexPathes. protocol DetailViewControllerDataSource { func numberOfSeasons() -> Int func numberOfEpisodesInSeason(_ seasonsIndex: Int) -> Int func setupCell(_ cell: EpisodeCell, seasonIndex: Int, episodeIndex: Int) func setupSeasonHeader(_ header: SeasonHeader, seasonIndex: Int) func cellWasPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) func cellWasLongPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) } class BaseDetailsViewController: BarHidingViewController, VDLPlaybackViewControllerDelegate, LoadingViewControllerDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, DetailViewControllerDataSource { // MARK: - Header related let headerMinAspectRatio: CGFloat = 0.4 let headerWidthToCollectionWidthKoef: CGFloat = 0.3 var header: StratchyHeader? var preferedOtherHeadersHeight: CGFloat = 35 var headerSize: CGSize { let width = collectionView.bounds.size.width let minHeight = width * headerMinAspectRatio var height = collectionView.bounds.size.height * headerWidthToCollectionWidthKoef height = max(height, minHeight) return CGSize(width: width, height: height) } // MARK: - let cellReuseIdentifier = "EpisodeCell" let firstHeaderReuseIdentifier = "StratchyHeader" let otherHeadersReuseIdentifier = "OtherHeader" let episodeCellReuseIdentifier = "EpisodeCell" var layout: StratchyHeaderLayout? var item: BasicInfo! { didSet { navigationItem.title = item.title reloadData() } } @IBOutlet weak var collectionView: UICollectionView!{ didSet{ collectionView.alwaysBounceVertical = true collectionView.dataSource = self collectionView.delegate = self collectionView.register(UINib(nibName: "StratchyHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: firstHeaderReuseIdentifier) collectionView.register(UINib(nibName: "SeasonHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: otherHeadersReuseIdentifier) collectionView.register(UINib(nibName: "EpisodeCell", bundle: nil), forCellWithReuseIdentifier: episodeCellReuseIdentifier) layout = collectionView.collectionViewLayout as? StratchyHeaderLayout } } // MARK: - View Life Cycle override func viewDidLoad() { super.viewDidLoad() configureFavoriteBarButton() let longPress = UILongPressGestureRecognizer(target: self, action: #selector(BaseDetailsViewController.longPress(_:))) longPress.minimumPressDuration = 0.5 longPress.delaysTouchesBegan = true collectionView.addGestureRecognizer(longPress) } final func longPress(_ gesture: UILongPressGestureRecognizer) { if gesture.state == .ended { return } let p = gesture.location(in: collectionView) if let indexPath = collectionView.indexPathForItem(at: p) { if let cell = collectionView.cellForItem(at: indexPath) { cellWasLongPressed(cell, seasonIndex: indexPath.section - 1, episodeIndex: indexPath.item) } } } func configureFavoriteBarButton() { if (item.isFavorite) { self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage.removeFromFavoritesImage(), style: .done, target: self, action: #selector(BaseDetailsViewController.removeFromFavorites)) } else { self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage.addToFavoritesImage(), style: .done, target: self, action: #selector(BaseDetailsViewController.addToFavorites)) } } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() // update header size header?.headerSize = headerSize layout?.headerSize = headerSize } // MARK: - Favorites func addToFavorites() { DataManager.sharedManager().addToFavorites(item) configureFavoriteBarButton() } func removeFromFavorites() { DataManager.sharedManager().removeFromFavorites(item) configureFavoriteBarButton() } // MARK: - BaseDetailsViewController func reloadData() { } func startPlayback(_ episode: Episode, basicInfo: BasicInfo, magnetLink: String, loadingTitle: String) { let loadingVC = self.storyboard?.instantiateViewController(withIdentifier: "loadingViewController") as! LoadingViewController loadingVC.delegate = self loadingVC.status = "Downloading..." loadingVC.loadingTitle = loadingTitle loadingVC.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext self.tabBarController?.present(loadingVC, animated: true, completion: nil) PTTorrentStreamer.shared().startStreaming(fromFileOrMagnetLink: magnetLink, progress: { (status) -> Void in loadingVC.progress = status.bufferingProgress loadingVC.speed = Int(status.downloadSpeed) loadingVC.seeds = Int(status.seeds) loadingVC.peers = Int(status.peers) }, readyToPlay: { (url) -> Void in loadingVC.dismiss(animated: false, completion: nil) let vdl = VDLPlaybackViewController(nibName: "VDLPlaybackViewController", bundle: nil) vdl.delegate = self self.navigationController?.present(vdl, animated: true, completion: nil) vdl.playMedia(from: url) }, failure: { (error) -> Void in loadingVC.dismiss(animated: true, completion: nil) }) } // MARK: - VDLPlaybackViewControllerDelegate func playbackControllerDidFinishPlayback(_ playbackController: VDLPlaybackViewController!) { self.navigationController?.dismiss(animated: true, completion: nil) PTTorrentStreamer.shared().cancelStreaming() } // MARK: - LoadingViewControllerDelegate func didCancelLoading(_ controller: LoadingViewController) { PTTorrentStreamer.shared().cancelStreaming() controller.dismiss(animated: true, completion: nil) } // MARK: - UICollectionViewDataSource final func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { switch section { case 0: return 0 default: let seasonIndex = section - 1 return self.numberOfEpisodesInSeason(seasonIndex) } } final func numberOfSections(in collectionView: UICollectionView) -> Int { return self.numberOfSeasons() + 1 // extra section for header } final func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let seasonIndex = indexPath.section - 1 let episode = indexPath.item let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseIdentifier, for: indexPath) as! EpisodeCell self.setupCell(cell, seasonIndex: seasonIndex, episodeIndex: episode) return cell } final func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { switch section { case 0: return headerSize default : return CGSize(width: collectionView.bounds.width, height: preferedOtherHeadersHeight) } } func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { if indexPath.section == 0 { if (header == nil){ header = (collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: firstHeaderReuseIdentifier, for: indexPath) as! StratchyHeader) header?.delegate = layout if let image = item.bigImage?.image { header?.image = image } else { ImageProvider.sharedInstance.imageFromURL(URL: item.bigImage?.URL) { (downloadedImage) -> () in self.item.bigImage?.image = downloadedImage self.header?.image = downloadedImage } } if let image = item.smallImage?.image { header?.foregroundImage.image = image } else { ImageProvider.sharedInstance.imageFromURL(URL: item.smallImage?.URL) { (downloadedImage) -> () in self.item.smallImage?.image = downloadedImage self.header?.foregroundImage.image = downloadedImage } } } header!.synopsisTextView.text = item.synopsis return header! } else { let otherHeader = (collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: otherHeadersReuseIdentifier, for: indexPath) as! SeasonHeader) let seasonIndex = (indexPath.section - 1) self.setupSeasonHeader(otherHeader, seasonIndex: seasonIndex) return otherHeader } } // MARK: - UICollectionViewDelegate final func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if let cell = collectionView.cellForItem(at: indexPath) { cellWasPressed(cell, seasonIndex: indexPath.section - 1, episodeIndex: indexPath.item) } } func showVideoPickerPopupForEpisode(_ episode: Episode, basicInfo: BasicInfo, fromView view: UIView) { let videos = episode.videos if (videos.count > 0) { let actionSheetController = UIAlertController(title: episode.title, message: episode.desc, preferredStyle: UIAlertControllerStyle.actionSheet) let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) actionSheetController.addAction(cancelAction) for video in videos { var title = "" if let subGroup = video.subGroup { title += "[\(subGroup)] " } if let quality = video.quality { title += quality } let action = UIAlertAction(title: title, style: UIAlertActionStyle.default, handler: { (action) -> Void in let magnetLink = video.magnetLink let episodeTitle = episode.title ?? "" let loadingTitle = "\(episodeTitle) - \(title)" self.startPlayback(episode, basicInfo: basicInfo , magnetLink: magnetLink, loadingTitle: loadingTitle) }) actionSheetController.addAction(action) } let popOver = actionSheetController.popoverPresentationController popOver?.sourceView = view popOver?.sourceRect = view.bounds popOver?.permittedArrowDirections = UIPopoverArrowDirection.any self.present(actionSheetController, animated: true, completion: nil) } } // MARK: - DetailViewControllerDataSource func numberOfSeasons() -> Int { assertionFailure("Should be overriden by subclass") return 0 } func numberOfEpisodesInSeason(_ seasonsIndex: Int) -> Int { assertionFailure("Should be overriden by subclass") return 0 } func setupCell(_ cell: EpisodeCell, seasonIndex: Int, episodeIndex: Int) { assertionFailure("Should be overriden by subclass") } func setupSeasonHeader(_ header: SeasonHeader, seasonIndex: Int) { assertionFailure("Should be overriden by subclass") } func cellWasPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) { assertionFailure("Should be overriden by subclass") } func cellWasLongPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) { } } ================================================ FILE: PopcornTime/Controllers/ColorfullTabBarController.swift ================================================ // // ColorfullTabBarController.swift // PopcornTime // // Created by Andrew K. on 4/10/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class ColorfullTabBarController: UITabBarController, UITabBarControllerDelegate { fileprivate struct ColorConstants { static let favoritesTintColor = UIColor(red: 235/255, green: 66/255, blue: 69/255, alpha: 1.0) static let moviesTintColor = UIColor(red: 66/255, green: 166/255, blue: 235/255, alpha: 1.0) static let showsTintColor = UIColor(red: 33/255, green: 181/255, blue: 42/255, alpha: 1.0) static let animeTintColor = UIColor(red: 235/255, green: 66/255, blue: 164/255, alpha: 1.0) } override func viewDidLoad() { super.viewDidLoad() self.delegate = self } deinit { NotificationCenter.default.removeObserver(self) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) assignColors() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) assignColors() } fileprivate func assignColors() { switch selectedIndex { case 0: view.window?.tintColor = ColorConstants.favoritesTintColor case 1: view.window?.tintColor = ColorConstants.moviesTintColor case 2: view.window?.tintColor = ColorConstants.showsTintColor case 3: view.window?.tintColor = ColorConstants.animeTintColor default: break } } // MARK: - UITabBarControllerDelegate func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { self.assignColors() } } ================================================ FILE: PopcornTime/Controllers/FavoritesViewController.swift ================================================ // // FavoritesViewController.swift // PopcornTime // // Created by Danylo Kostyshyn on 3/13/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class FavoritesViewController: PagedViewController { override func reloadData() { if let favoriteItems = DataManager.sharedManager().favorites { self.items = favoriteItems self.collectionView?.reloadData() } } override func loadMore() { } // MARK: View Life Cycle override func viewDidLoad() { super.viewDidLoad() // No search in favorites self.searchController = nil self.navigationItem.titleView = nil NotificationCenter.default.addObserver( self, selector: #selector(FavoritesViewController.favoritesDidChange(_:)), name: NSNotification.Name(rawValue: Notifications.FavoritesDidChangeNotification), object: nil ) } deinit { NotificationCenter.default.removeObserver(self) } // MARK: - Navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let item = sender as? BasicInfo { if let episodesVC = segue.destination as? BaseDetailsViewController { switch item { case item as Anime: episodesVC.item = item as! Anime case item as Show: episodesVC.item = item as! Show case item as Movie: episodesVC.item = item as! Movie default: break } } } } // MARK: - Notifications func favoritesDidChange(_ notification: Notification) { self.reloadData() } // MARK: - UICollectionViewDelegate func collectionView(_ collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: IndexPath) { let item = self.items[indexPath.row] switch item { case item as Anime: performSegue(withIdentifier: "showDetailsForFavoriteAnime", sender: item) case item as Show: performSegue(withIdentifier: "showDetailsForFavoriteShow", sender: item) case item as Movie: performSegue(withIdentifier: "showDetailsForFavoriteMovie", sender: item) default: break } } } ================================================ FILE: PopcornTime/Controllers/LoadingViewController.swift ================================================ // // LoadingViewController.swift // PopcornTime // // Created by Danylo Kostyshyn on 3/15/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit protocol LoadingViewControllerDelegate { func didCancelLoading(_ controller: LoadingViewController) } class LoadingViewController: UIViewController { var delegate: LoadingViewControllerDelegate? @IBOutlet fileprivate weak var statusLabel: UILabel! @IBOutlet fileprivate weak var progressLabel: UILabel! @IBOutlet fileprivate weak var progressView: UIProgressView! @IBOutlet fileprivate weak var speedLabel: UILabel! @IBOutlet fileprivate weak var seedsLabel: UILabel! @IBOutlet fileprivate weak var peersLabel: UILabel! @IBOutlet fileprivate weak var titleLabel: UILabel! var status: String? = nil { didSet { if let status = status { statusLabel?.text = status } } } var progress: Float = 0.0 { didSet { progressView.progress = progress progressLabel.text = String(format: "%.0f%%", progress*100) } } var speed: Int = 0 { // bytes/s didSet { let formattedSpeed = ByteCountFormatter.string(fromByteCount: Int64(speed), countStyle: .binary) + "/s" speedLabel.text = String(format:"Speed: %@", formattedSpeed) } } var seeds: Int = 0 { didSet { seedsLabel.text = String(format: "Seeds: %d", seeds) } } var peers: Int = 0 { didSet { peersLabel.text = String(format: "Peers: %d", peers) } } var loadingTitle: String? = nil { didSet { if let title = loadingTitle { titleLabel?.text = title } } } // MARK: - View Life Cycle override func viewDidLoad() { super.viewDidLoad() titleLabel?.text = loadingTitle status = "Loading..." progress = 0.0 speed = 0 seeds = 0 peers = 0 UIApplication.shared.isIdleTimerDisabled = true; } override func viewDidDisappear(_ animated: Bool) { super.viewDidLoad() UIApplication.shared.isIdleTimerDisabled = false; } // MARK: - Actions @IBAction fileprivate func cancelButtonPressed(_ sender: AnyObject) { delegate?.didCancelLoading(self) } } ================================================ FILE: PopcornTime/Controllers/MovieDetailsViewController.swift ================================================ // // ShowDetailsViewController.swift // PopcornTime // // Created by Andrew K. on 3/13/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class MovieDetailsViewController: BaseDetailsViewController { var movie: Movie! { get { return self.item as! Movie } } // MARK: - UIViewController override func viewDidLoad() { super.viewDidLoad() preferedOtherHeadersHeight = 0.0 } // MARK: - BaseDetailsViewController override func reloadData() { PTAPIManager.shared().showInfo(with: .movie, withId: item.identifier, success: { (item) -> Void in guard let item = item else { return } self.movie.update(item) self.collectionView?.reloadData() }, failure: nil) } // MARK: - DetailViewControllerDataSource override func numberOfSeasons() -> Int { return 1 } override func numberOfEpisodesInSeason(_ seasonsIndex: Int) -> Int { return movie.videos.count } override func setupCell(_ cell: EpisodeCell, seasonIndex: Int, episodeIndex: Int) { let video = movie.videos[episodeIndex] var title = "" if let quality = video.quality { title += quality + " " } if let name = video.name { title += name } cell.titleLabel.text = title } override func setupSeasonHeader(_ header: SeasonHeader, seasonIndex: Int) { } override func cellWasPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) { let video = movie.videos[episodeIndex] let magnetLink = video.magnetLink let title = movie.title ?? "" let fakeEpisode = Episode(title: title, desc: "", seasonNumber: 0, episodeNumber: 0, videos: [Video]()) startPlayback(fakeEpisode, basicInfo: movie, magnetLink: magnetLink, loadingTitle: title) } } ================================================ FILE: PopcornTime/Controllers/MoviesViewController.swift ================================================ // // MoviesViewController.swift // PopcornTime // // Created by Andrew K. on 3/19/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class MoviesViewController: PagedViewController { override var showType: PTItemType { get { return .movie } } override func map(_ response: [AnyObject]) -> [BasicInfo] { let items = response.map({ Movie(dictionary: $0 as! [AnyHashable: Any]) }) as [BasicInfo] return items } func collectionView(_ collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: IndexPath) { if let cell = collectionView.cellForItem(at: indexPath){ //Check if cell is MoreShowsCell if let _ = cell as? MoreShowsCollectionViewCell{ loadMore() } else { performSegue(withIdentifier: "showDetails", sender: cell) } } } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { super.prepare(for: segue, sender: sender) if segue.identifier == "showDetails"{ if let episodesVC = segue.destination as? MovieDetailsViewController{ if let senderCell = sender as? UICollectionViewCell{ if let indexPath = collectionView!.indexPath(for: senderCell) { var item: BasicInfo! if (searchController!.isActive) { item = searchResults[indexPath.row] } else { item = items[indexPath.row] } episodesVC.item = item as! Movie } } } } } } ================================================ FILE: PopcornTime/Controllers/OAuthViewController.swift ================================================ // // WebViewController.swift // PopcornTime // // Created by Danylo Kostyshyn on 3/15/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit protocol OAuthViewControllerDelegate { func oauthViewControllerDidFinish(_ controller: OAuthViewController, token: String?, error: NSError?) } class OAuthViewController: UIViewController, UIWebViewDelegate { @IBOutlet weak var webView: UIWebView! @IBOutlet weak var navigationBar: UINavigationBar! var delegate: OAuthViewControllerDelegate? var URL: Foundation.URL? // MARK: - View Life Cycle override func viewDidLoad() { super.viewDidLoad() if let URL = URL { webView.loadRequest(URLRequest(url: URL)) } } // MARK: - UIWebViewDelegate func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool { if let code = request.url?.lastPathComponent { if code.characters.count == 64 { // PTAPIManager.sharedManager().accessTokenWithAuthorizationCode(code, success: { (accessToken) -> Void in // println("OAuth access token: \(accessToken)") // self.delegate?.oauthViewControllerDidFinish(self, token: accessToken, error: nil) // }, failure: { (error) -> Void in // println("\(error)") // self.delegate?.oauthViewControllerDidFinish(self, token: nil, error: error) // }) } } return true; } func webViewDidStartLoad(_ webView: UIWebView) { } func webViewDidFinishLoad(_ webView: UIWebView) { } func webView(_ webView: UIWebView, didFailLoadWithError error: Error) { delegate?.oauthViewControllerDidFinish(self, token: nil, error: error as NSError?) } // MARK: - Actions @IBAction func cancelButtonPressed(_ sender: AnyObject) { delegate?.oauthViewControllerDidFinish(self, token: nil, error: nil) } } ================================================ FILE: PopcornTime/Controllers/PagedViewController.swift ================================================ // // PagedViewController.swift // PopcornTime // // Created by Andrew K. on 3/19/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class PagedViewController: BaseCollectionViewController, UISearchBarDelegate, UISearchResultsUpdating { fileprivate var contentPage: UInt = 0 var searchResults = [BasicInfo]() var searchController: UISearchController? var searchTimer: Timer? var showType: PTItemType { get { assert(false, "this must be overriden by subclass") return .movie } } // MARK: View Life Cycle override func viewDidLoad() { super.viewDidLoad() setupSearch() } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) collectionViewLayout?.invalidateLayout() } fileprivate func setupSearch() { self.definesPresentationContext = true searchController = UISearchController(searchResultsController: nil) searchController!.searchResultsUpdater = self searchController!.hidesNavigationBarDuringPresentation = false searchController!.dimsBackgroundDuringPresentation = false let searchBar = searchController!.searchBar searchBar.delegate = self searchBar.barStyle = .black searchBar.backgroundImage = UIImage() let searchBarContainer = UIView(frame: navigationController!.navigationBar.bounds) searchBarContainer.addSubview(searchBar) searchBar.translatesAutoresizingMaskIntoConstraints = false navigationItem.titleView = searchBarContainer let views = ["searchBar" : searchBar] searchBarContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[searchBar]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)) searchBarContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[searchBar]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)) } // MARK: func map(_ response: [AnyObject]) -> [BasicInfo] { return [BasicInfo]() } override func reloadData() { PTAPIManager.shared().topShows(with: showType, withPage: contentPage, success: { (items) -> Void in self.showLoadMoreCell = true if let items = items { self.items = self.map(items as [AnyObject]) self.collectionView?.reloadData() } }, failure: nil) } func loadMore() { PTAPIManager.shared().topShows(with: showType, withPage: contentPage+1, success: { (items) -> Void in if let items = items { self.contentPage += 1 let newItems = self.map(items as [AnyObject]) let newShowsIndexPathes = newItems.enumerated().map({ (index, item) in return IndexPath(row: (self.items.count + index), section: 0) }) self.items += newItems self.collectionView?.insertItems(at: newShowsIndexPathes) } }, failure: nil) } func performSearch() { let text = searchController!.searchBar.text if text!.characters.count > 0 { PTAPIManager.shared().searchForShow(with: showType, name: text, success: { (items) -> Void in self.showLoadMoreCell = false if let items = items { self.searchResults = self.map(items as [AnyObject]) } else { self.searchResults.removeAll(keepingCapacity: false) } self.collectionView?.reloadData() }, failure: nil) } } // MARK: UICollectionViewDataSource override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { if searchController != nil && searchController!.isActive { return searchResults.count } return super.collectionView(collectionView, numberOfItemsInSection: section) } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { if searchController != nil && searchController!.isActive { //Ordinary show cell let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifierShow, for: indexPath) as! ShowCollectionViewCell let item = searchResults[indexPath.row] cell.title = item.title let imageItem = item.smallImage switch imageItem?.status { case .new?: imageItem?.status = .downloading ImageProvider.sharedInstance.imageFromURL(URL: imageItem?.URL) { (downloadedImage) -> () in imageItem?.image = downloadedImage imageItem?.status = .finished collectionView.reloadItems(at: [indexPath]) } case .finished?: cell.image = imageItem?.image default: break } return cell } return super.collectionView(collectionView, cellForItemAt: indexPath) } // MARK: UICollectionViewDelegate func collectionView(_ collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: IndexPath) { if indexPath.row == items.count { loadMore() } } // MARK: UISearchBarDelegate func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { self.searchTimer?.invalidate() self.searchTimer = nil performSearch() } func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { self.showLoadMoreCell = true self.searchTimer?.invalidate() self.searchTimer = nil searchResults.removeAll(keepingCapacity: false) self.collectionView.reloadData() } // MARK: UISearchResultsUpdating func updateSearchResults(for searchController: UISearchController) { self.searchTimer?.invalidate() self.collectionView.reloadData() self.searchTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(PagedViewController.performSearch), userInfo: nil, repeats: false) } } ================================================ FILE: PopcornTime/Controllers/ParseViewController.swift ================================================ // // ParseViewController.swift // // // Created by Andriy K. on 6/22/15. // // import UIKit class ParseViewController: UIViewController, PFLogInViewControllerDelegate { private var canPromptLogin = true // MARK: - UIViewController override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) promptLoginIfNeeded(true) } // MARK: - Login func promptLoginIfNeeded(animated: Bool) { let currentUser = ParseManager.sharedInstance.user if currentUser == nil { // Show the signup or login screen let logInController = PFLogInViewController() logInController.delegate = self logInController.fields = [PFLogInFields.DismissButton, PFLogInFields.Facebook] logInController.facebookPermissions = ["public_profile"] self.presentViewController(logInController, animated:animated, completion: nil) } } // MARK: PFLogInViewControllerDelegate func logInViewController(logInController: PFLogInViewController, didLogInUser user: PFUser) { dissmiss(nil) } func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?) { } func logInViewControllerDidCancelLogIn(logInController: PFLogInViewController) { dissmiss(nil) dissmiss(nil) } // MARK: - Actions @IBAction func dissmiss(sender: AnyObject?) { canPromptLogin = false self.dismissViewControllerAnimated(true, completion: nil) } @IBAction func logOutPressed(sender: UIBarButtonItem) { PFUser.logOut() dissmiss(nil) } @IBAction func clearAllDataPressed(sender: UIBarButtonItem) { } } ================================================ FILE: PopcornTime/Controllers/SettingsViewController.swift ================================================ // // SettingsViewController.swift // PopcornTime // // Created by Danylo Kostyshyn on 4/4/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class SettingsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { // MARK: - View Life Cycle override func viewDidLoad() { super.viewDidLoad() } // MARK: - func appInfoString() -> String { let displayName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as! String let version = Bundle.main.infoDictionary?["CFBundleVersion"] as! String let shortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String return "\(displayName) \(shortVersion) (\(version))" } // MARK: - UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let identifier = "SettingsCell" var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "SettingsCell") if cell == nil { cell = UITableViewCell(style: .default, reuseIdentifier: identifier) } cell.textLabel?.text = "Hello, PopcornTime!" return cell } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { let label = UILabel(frame: CGRect(x: 0.0, y: 0.0, width: tableView.bounds.width, height: 0.0)) label.backgroundColor = UIColor.clear label.font = UIFont.systemFont(ofSize: 14.0) label.text = appInfoString() label.textAlignment = .center return label } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 30.0 } // MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView .deselectRow(at: indexPath, animated: true) } @IBAction func doneButtonTapped(_ sender: AnyObject) { self.dismiss(animated: true, completion: nil) } } ================================================ FILE: PopcornTime/Controllers/ShowDetailsViewController.swift ================================================ // // ShowDetailsViewController.swift // PopcornTime // // Created by Andrew K. on 3/13/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class ShowDetailsViewController: BaseDetailsViewController { var show: Show! { get { return self.item as! Show } } // MARK: - BaseDetailsViewController override func reloadData() { PTAPIManager.shared().showInfo(with: .show, withId: show.identifier, success: { (item) -> Void in guard let item = item else { return } self.show.update(item) self.collectionView?.reloadData() }, failure: nil) } // MARK: - DetailViewControllerDataSource override func numberOfSeasons() -> Int { return show.seasons.count } override func numberOfEpisodesInSeason(_ seasonsIndex: Int) -> Int { return show.seasons[seasonsIndex].episodes.count } override func setupCell(_ cell: EpisodeCell, seasonIndex: Int, episodeIndex: Int) { let episode = show.seasons[seasonIndex].episodes[episodeIndex] if let title = episode.title { cell.titleLabel.text = "S\(episode.seasonNumber)E\(episode.episodeNumber): \(title)" } else { cell.titleLabel.text = "S\(episode.seasonNumber)E\(episode.episodeNumber)" } } override func setupSeasonHeader(_ header: SeasonHeader, seasonIndex: Int) { let seasonNumber = self.show.seasons[seasonIndex].seasonNumber header.titleLabel.text = "Season \(seasonNumber)" } override func cellWasPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) { let episode = show.episodeFor(seasonIndex: seasonIndex, episodeIndex: episodeIndex) showVideoPickerPopupForEpisode(episode, basicInfo: self.item, fromView: cell) } override func cellWasLongPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) { // let episode = show.episodeFor(seasonIndex: seasonIndex, episodeIndex: episodeIndex) // let seasonEpisodes = show.episodesFor(seasonIndex: seasonIndex) } } ================================================ FILE: PopcornTime/Controllers/ShowsViewController.swift ================================================ // // TVSeriesShowsViewController.swift // PopcornTime // // Created by Andrew K. on 3/9/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class ShowsViewController: PagedViewController { override var showType: PTItemType { get { return .show } } override func map(_ response: [AnyObject]) -> [BasicInfo] { return response.map({ Show(dictionary: $0 as! [AnyHashable: Any]) }) } func collectionView(_ collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: IndexPath) { if let cell = collectionView.cellForItem(at: indexPath){ //Check if cell is MoreShowsCell if let _ = cell as? MoreShowsCollectionViewCell { loadMore() } else { performSegue(withIdentifier: "showDetails", sender: cell) } } } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { super.prepare(for: segue, sender: sender) if segue.identifier == "showDetails" { if let episodesVC = segue.destination as? ShowDetailsViewController { if let senderCell = sender as? UICollectionViewCell { if let indexPath = collectionView!.indexPath(for: senderCell) { var item: BasicInfo! if (searchController!.isActive) { item = searchResults[indexPath.row] } else { item = items[indexPath.row] } episodesVC.item = item as! Show } } } } } } ================================================ FILE: PopcornTime/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName Popcorn Time CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 Fabric APIKey API_KEY Kits KitInfo KitName Crashlytics LSRequiresIPhoneOS NSAppTransportSecurity NSAllowsArbitraryLoads UILaunchStoryboardName Launch Screen UIMainStoryboardFile Main UIRequiredDeviceCapabilities UIStatusBarHidden UIStatusBarStyle UIStatusBarStyleBlackOpaque UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: PopcornTime/Models/APIManager.swift ================================================ // // APIManager.swift // popcornTime // // Created by Danylo Kostyshyn on 3/13/15. // Copyright (c) 2015 Danylo Kostyshyn. All rights reserved. // /* http://ytspt.re/api/list.json?limit=30&order=desc&sort=seeds http://ytspt.re/api/listimdb.json?imdb_id=tt2245084 http://ytspt.re/api/list.json?limit=30&keywords=terminator&order=desc&sort=seeds&set=1 http://www.yifysubtitles.com//subtitle-api/big-hero-6-yify-36523.zip http://eztvapi.re/shows/1?limit=30&order=desc&sort=seeds http://eztvapi.re/show/tt0898266 http://eztvapi.re/shows/1?limit=30&keywords=the+big+bang&order=desc&sort=seeds http://ptp.haruhichan.com/list.php? http://ptp.haruhichan.com/anime.php?id=912 */ import Foundation class APIManager { typealias APIManagerFailure = (error: NSError?) -> () typealias APIManagerSuccessItems = (items: [AnyObject]?) -> () typealias APIManagerSuccessItem = (item: [String: AnyObject]?) -> () private let APIManagerMoviesEndPoint = "http://ytspt.re/api" private let APIManagerShowsEndPoint = "http://eztvapi.re" private let APIManagerResultsLimit = 30 class func sharedManager() -> APIManager { struct Static { static let instance: APIManager = APIManager() } return Static.instance } private func data(url: NSURL, sucess: ((AnyObject?) -> ())?, failure: APIManagerFailure?) { NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { (data, response, error) -> Void in var serializationError: NSError? var JSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &serializationError) if let serializationError = serializationError { println("\(serializationError)") if let failure = failure { failure(error: serializationError) } } if let sucess = sucess { sucess(JSONObject) } }).resume() } // MARK: Movies func topMovies(success: APIManagerSuccessItems?, failure: APIManagerFailure?) { var path = String(format: "list.json?limit=%d&order=desc&sort=seeds", APIManagerResultsLimit) var url = NSURL(string: APIManagerMoviesEndPoint.stringByAppendingPathComponent(path)) data(url!, sucess: { (JSONObject) -> () in if let success = success { var dict = JSONObject as [String: AnyObject] success(items: dict["MovieList"] as [AnyObject]?) } }, failure: failure) } func movieInfo(imdbId: String, success: APIManagerSuccessItems?, failure: APIManagerFailure?) { var path = String(format: "listimdb.json?imdb_id=%@", imdbId) var url = NSURL(string: APIManagerMoviesEndPoint.stringByAppendingPathComponent(path)) data(url!, sucess: { (JSONObject) -> () in if let success = success { var dict = JSONObject as [String: AnyObject] success(items: dict["MovieList"] as [AnyObject]?) } }, failure: failure) } func searchMovie(name: String, success: APIManagerSuccessItems?, failure: APIManagerFailure?) { var path = String(format: "list.json?limit=%lu&keywords=%@&order=desc&sort=seeds&set=1", APIManagerResultsLimit, name) var url = NSURL(string: APIManagerMoviesEndPoint.stringByAppendingPathComponent(path)) data(url!, sucess: { (JSONObject) -> () in if let success = success { var dict = JSONObject as [String: AnyObject] success(items: dict["MovieList"] as [AnyObject]?) } }, failure: failure) } // MARK: Shows func topShows(page: UInt, success: APIManagerSuccessItems?, failure: APIManagerFailure?) { var path = String(format: "shows/%lu?limit=%lu&order=desc&sort=seeds", (page + 1), APIManagerResultsLimit) var url = NSURL(string: APIManagerShowsEndPoint.stringByAppendingPathComponent(path)) data(url!, sucess: { (JSONObject) -> () in if let success = success { success(items: JSONObject as [AnyObject]?) } }, failure: failure) } func showInfo(imdbId: String, success: APIManagerSuccessItem?, failure: APIManagerFailure?) { var path = String(format: "show/%@", imdbId) var url = NSURL(string: APIManagerShowsEndPoint.stringByAppendingPathComponent(path)) data(url!, sucess: { (JSONObject) -> () in if let success = success { success(item: JSONObject as [String: AnyObject]?) } }, failure: failure) } func searchShow(name: String, success: APIManagerSuccessItems?, failure: APIManagerFailure?) { var path = String(format: "shows/1?limit=%lu&keywords=%@&sort=seeds", APIManagerResultsLimit, name) var url = NSURL(string: APIManagerShowsEndPoint.stringByAppendingPathComponent(path)) data(url!, sucess: { (JSONObject) -> () in if let success = success { success(items: JSONObject as [AnyObject]?) } }, failure: failure) } } ================================================ FILE: PopcornTime/Models/Anime.swift ================================================ // // Anime.swift // PopcornTime // // Created by Danylo Kostyshyn on 3/19/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import Foundation class Anime: BasicInfo { var seasons = [Season]() required init(dictionary: [AnyHashable: Any]) { super.init(dictionary: dictionary) let id = dictionary["id"] as! Int identifier = "\(id)" title = dictionary["name"] as? String year = dictionary["name"] as? String if let poster = dictionary["malimg"] as? String { images = [Image]() let URL = Foundation.URL(string: poster) let image = Image(URL: URL!, type: .poster) images.append(image) } smallImage = images.filter({$0.type == ImageType.poster}).first bigImage = smallImage } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! } override func update(_ dictionary: [AnyHashable: Any]) { seasons.removeAll(keepingCapacity: true) let episodesDicts = dictionary["episodes"] as! [[AnyHashable: Any]] let seasonNumber:UInt = 0 var videosContainer = [Int: [Video]]() var episodesContainer = [Int: Episode]() synopsis = dictionary["synopsis"] as? String if let sps = synopsis { synopsis = sps.replacingOccurrences(of: "oatRightHeader\">EditSynopsis\n", with: "") } for episodeDict in episodesDicts{ let title = episodeDict["name"] as! String let numbersFromTitle = numbersFromAnimeTitle(title) let synopsis = episodeDict["overview"] as? String if numbersFromTitle.count > 0 { if let quality = episodeDict["quality"] as? String{ // Get entry data let subGroup = episodeDict["subgroup"] as? String let episodeNumber = numbersFromTitle.first! let magnetLink = episodeDict["magnet"] as! String let video = Video(name: title, quality: quality, size: 0, duration: 0, subGroup: subGroup, magnetLink: magnetLink) var videos = videosContainer[episodeNumber] if (videos == nil) { videos = [Video]() videosContainer[episodeNumber] = videos } videosContainer[episodeNumber]!.append(video) var episode = episodesContainer[episodeNumber] if (episode == nil) { episode = Episode(title: title, desc: synopsis, seasonNumber: seasonNumber, episodeNumber: UInt(episodeNumber), videos: [Video]()) episodesContainer[episodeNumber] = episode! } } } } for entry in videosContainer { let episodeNumber = entry.0 let videos = entry.1 episodesContainer[episodeNumber]!.videos = videos } let episodes = Array(episodesContainer.values).sorted { (a, b) -> Bool in return a.episodeNumber > b.episodeNumber } let season = Season(seasonNumber: seasonNumber, episodes: episodes) seasons.append(season) } fileprivate func numbersFromAnimeTitle(_ title: String) -> [Int]{ let components = title.components(separatedBy: CharacterSet(charactersIn: "[]_() ")) var numbers = [Int]() for component in components { if let number = Int(component) { if number < 10000 { numbers.append(number) } } } return numbers } } extension Anime: ContainsEpisodes { func episodeFor(seasonIndex: Int, episodeIndex: Int) -> Episode { let episode = seasons[seasonIndex].episodes[episodeIndex] return episode } func episodesFor(seasonIndex: Int) -> [Episode] { return seasons[seasonIndex].episodes } } ================================================ FILE: PopcornTime/Models/AppDelegate.swift ================================================ // // AppDelegate.swift // PopcornTime // // Created by Danylo Kostyshyn on 3/13/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit #if RELEASE import Fabric import Crashlytics #endif @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { #if RELEASE Fabric.with([Crashlytics()]) #endif return true } } ================================================ FILE: PopcornTime/Models/BaseStructures.swift ================================================ // // Video.swift // PopcornTime // // Created by Danylo Kostyshyn on 3/21/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // struct Video { let name: String? let quality: String? let size: UInt? let duration: UInt? let subGroup: String? let magnetLink: String } struct Episode { let title: String? let desc: String? let seasonNumber: UInt let episodeNumber: UInt var videos = [Video]() } struct Season { let seasonNumber: UInt let episodes: [Episode] } ================================================ FILE: PopcornTime/Models/BasicInfo.swift ================================================ // // BasicInfo.swift // PopcornTime // // Created by Danylo Kostyshyn on 3/19/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import Foundation protocol ContainsEpisodes { func episodeFor(seasonIndex: Int, episodeIndex: Int) -> Episode func episodesFor(seasonIndex: Int) -> [Episode] } protocol BasicInfoProtocol { var identifier: String! {get} var title: String? {get} var year: String? {get} var images: [Image]! {get} var smallImage: Image? {get} var bigImage: Image? {get} var isFavorite: Bool {get} init(dictionary: [AnyHashable: Any]) func update(_ dictionary: [AnyHashable: Any]) } class BasicInfo: NSObject, BasicInfoProtocol, NSCoding { var identifier: String! var title: String? var year: String? var images: [Image]! var smallImage: Image? var bigImage: Image? var synopsis: String? var isFavorite : Bool { get { return DataManager.sharedManager().isFavorite(self) } set { if (isFavorite == true) { return DataManager.sharedManager().addToFavorites(self) } else { return DataManager.sharedManager().removeFromFavorites(self) } } } required init(dictionary: [AnyHashable: Any]) { // fatalError("init(dictionary:) has not been implemented") } func update(_ dictionary: [AnyHashable: Any]) { fatalError("update(dictionary:) has not been implemented") } // MARK: - NSCoding required init?(coder aDecoder: NSCoder) { guard let identifier = aDecoder.decodeObject(forKey: "identifier") as? String else { return nil } self.identifier = identifier self.title = aDecoder.decodeObject(forKey: "title") as? String self.year = aDecoder.decodeObject(forKey: "year") as? String self.smallImage = aDecoder.decodeObject(forKey: "smallImage") as? Image self.bigImage = aDecoder.decodeObject(forKey: "bigImage") as? Image } func encode(with aCoder: NSCoder) { aCoder.encode(identifier, forKey: "identifier") aCoder.encode(title, forKey: "title") aCoder.encode(year, forKey: "year") aCoder.encode(smallImage, forKey: "smallImage") aCoder.encode(bigImage, forKey: "bigImage") } } ================================================ FILE: PopcornTime/Models/DataManager.swift ================================================ // // DataManager.swift // PopcornTime // // Created by Danylo Kostyshyn on 3/24/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import Foundation struct Notifications { static let FavoritesDidChangeNotification = "FavoritesDidChangeNotification" } class DataManager { let documentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! as String let fileName = "Favorites.plist" var filePath: String { get { return fileURL.path } } var fileURL: URL { let url = URL(fileURLWithPath: documentsDirectory, isDirectory: true) return url.appendingPathComponent(fileName) } var favorites: [BasicInfo]? init() { loadFavorites() } class func sharedManager() -> DataManager { struct Static { static let instance: DataManager = DataManager() } return Static.instance } fileprivate func loadFavorites() -> [BasicInfo]? { if FileManager.default.fileExists(atPath: filePath) { let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) if let data = data { self.favorites = NSKeyedUnarchiver.unarchiveObject(with: data) as! [BasicInfo]? return self.favorites } } return nil } fileprivate func saveFavorites(_ items: [BasicInfo]) { let data = NSKeyedArchiver.archivedData(withRootObject: items) try? data.write(to: URL(fileURLWithPath: filePath), options: [.atomic]) self.favorites = items } // MARK: - func isFavorite(_ item: BasicInfo) -> Bool { let favoriteItem = self.favorites?.filter({ $0.identifier == item.identifier }).first if favoriteItem != nil { return true } return false } func addToFavorites(_ item: BasicInfo) { var items = [BasicInfo]() if let favorites = loadFavorites() { items += favorites } items.append(item) saveFavorites(items) NotificationCenter.default.post(name: Notification.Name(rawValue: Notifications.FavoritesDidChangeNotification), object: nil) } func removeFromFavorites(_ item: BasicInfo) { let items = loadFavorites() if var items = items { let favoriteItem = items.filter({ $0.identifier == item.identifier }).first if let favoriteItem = favoriteItem { let idx = items.index(of: favoriteItem) items.remove(at: idx!) saveFavorites(items) NotificationCenter.default.post(name: Notification.Name(rawValue: Notifications.FavoritesDidChangeNotification), object: nil) } } } } ================================================ FILE: PopcornTime/Models/Extensions.swift ================================================ // // UIImage+PopcornTime.swift // PopcornTime // // Created by Danylo Kostyshyn on 3/30/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // extension UIImage { class func addToFavoritesImage() -> UIImage? { return UIImage(named: "AddToFavoritesIcon") } class func removeFromFavoritesImage() -> UIImage? { return UIImage(named: "RemoveFromFavoritesIcon") } } ================================================ FILE: PopcornTime/Models/Image.swift ================================================ // // File.swift // PopcornTime // // Created by Danylo Kostyshyn on 3/21/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit enum ImageType: Int { case banner, poster, fanart } enum ImageStatus { case new, downloading, finished } class Image: NSObject, NSCoding { let URL: Foundation.URL var image: UIImage? let type: ImageType var status: ImageStatus = .new init(URL: Foundation.URL, type: ImageType) { self.URL = URL self.type = type } // MARK: - NSCoding required init?(coder aDecoder: NSCoder) { let typeRaw = aDecoder.decodeInteger(forKey: "type") guard let URL = aDecoder.decodeObject(forKey: "URL") as? Foundation.URL, let type = ImageType(rawValue: typeRaw) else { return nil } self.URL = URL self.type = type } func encode(with aCoder: NSCoder) { aCoder.encode(URL, forKey: "URL") aCoder.encode(type.rawValue, forKey: "type") } } ================================================ FILE: PopcornTime/Models/ImageProvider.swift ================================================ // // ImageProvider.swift // PopcornTime // // Created by Andrew K. on 3/10/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class ImageProvider: NSObject { static let sharedInstance = ImageProvider() func imageFromURL(URL: Foundation.URL?, completionBlock: @escaping (_ downloadedImage: UIImage?)->()) { guard let URL = URL else { return } SDWebImageDownloader.shared().downloadImage(with: URL, options: [SDWebImageDownloaderOptions.useNSURLCache], progress: nil) { (image, data, error, finished) -> Void in if let _ = error { NSLog("\(#function): \(error)") } DispatchQueue.main.async(execute: { completionBlock(image) }) } } } ================================================ FILE: PopcornTime/Models/Movie.swift ================================================ // // Movie.swift // PopcornTime // // Created by Danylo Kostyshyn on 3/19/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import Foundation class Movie: BasicInfo { var videos = [Video]() required init(dictionary: [AnyHashable: Any]) { super.init(dictionary: dictionary) let id = (dictionary["id"] as! NSString).intValue identifier = "\(id)" title = dictionary["title"] as? String year = String(describing: dictionary["year"]) images = [Image]() if let cover = dictionary["poster_med"] as? String { let image = Image(URL: URL(string: cover)!, type: .poster) images.append(image) } if let cover = dictionary["poster_big"] as? String { let image = Image(URL: URL(string: cover)!, type: .banner) images.append(image) } smallImage = self.images.filter({$0.type == ImageType.poster}).first bigImage = self.images.filter({$0.type == ImageType.banner}).first synopsis = dictionary["description"] as? String } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! } override func update(_ dictionary: [AnyHashable: Any]) { videos.removeAll(keepingCapacity: true) let title = dictionary["title"] as! String guard let movieList = dictionary["items"] as? [[AnyHashable: Any]] else { return } for movieDict in movieList { let quality = movieDict["quality"] as! String let magnetLink = movieDict["torrent_magnet"] as! String let size = movieDict["size_bytes"] as! UInt let video = Video(name: title, quality: quality, size: size, duration: 0, subGroup: nil, magnetLink: magnetLink) videos.append(video) } } } ================================================ FILE: PopcornTime/Models/PTAPIManager.h ================================================ // // PTAPIManager.h // PopcornTime // // Created by Danylo Kostyshyn on 2/25/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // #import typedef void (^PTAPIManagerFailure)(NSError *error); typedef void (^PTAPIManagerSuccessItems)(NSArray *items); typedef void (^PTAPIManagerSuccessItem)(NSDictionary *item); typedef void (^PTAPIManagerSuccessNone)(); typedef NS_ENUM(NSInteger, PTItemType) { PTItemTypeMovie, PTItemTypeShow, PTItemTypeAnime, }; @interface PTAPIManager : NSObject + (instancetype)sharedManager; - (void)showInfoWithType:(PTItemType)type withId:(NSString *)imdbId success:(PTAPIManagerSuccessItem)success failure:(PTAPIManagerFailure)failure; - (void)searchForShowWithType:(PTItemType)type name:(NSString *)name success:(PTAPIManagerSuccessItems)success failure:(PTAPIManagerFailure)failure; - (void)topShowsWithType:(PTItemType)type withPage:(NSUInteger)page success:(PTAPIManagerSuccessItems)success failure:(PTAPIManagerFailure)failure; // Trakt.tv /* + (NSString *)trakttvAccessToken; + (void)updateTrakttvAccessToken:(NSString *)accessToken; + (NSURL *)trakttvAuthorizationURL; - (void)accessTokenWithAuthorizationCode:(NSString *)authorizationCode success:(void(^)(NSString *accessToken))success failure:(PTAPIManagerFailure)failure; - (void)createListWithName:(NSString *)name success:(PTAPIManagerSuccessNone)success failure:(PTAPIManagerFailure)failure; */ @end ================================================ FILE: PopcornTime/Models/PTAPIManager.m ================================================ // // PTAPIManager.m // PopcornTime // // Created by Danylo Kostyshyn on 2/25/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // #import "PTAPIManager.h" #import NSUInteger const PTAPIManagerResultsLimit = 30; NSString *const PTAPIManagerMoviesEndPoint = @"http://api.torrentsapi.com/"; NSString *const PTAPIManagerShowsEndPoint = @"https://api-fetch.website/tv/"; NSString *const PTAPIManagerAnimeEndPoint = @"http://ptp.haruhichan.com"; @implementation PTAPIManager static NSDictionary *YTSHTTPHeaders; #pragma mark - Public API + (void)initialize { YTSHTTPHeaders = @{@"Host": @"eqwww.image.yt"}; } + (instancetype)sharedManager { static dispatch_once_t onceToken; static PTAPIManager *sharedManager; dispatch_once(&onceToken, ^{ sharedManager = [[PTAPIManager alloc] init]; }); return sharedManager; } - (void)showInfoWithType:(PTItemType)type withId:(NSString *)imdbId success:(PTAPIManagerSuccessItem)success failure:(PTAPIManagerFailure)failure { switch (type) { case PTItemTypeMovie: { [self movieInfoWithId:imdbId success:success failure:failure]; break; } case PTItemTypeShow: { [self tvSeriesInfoWithId:imdbId success:success failure:failure]; break; } case PTItemTypeAnime: { [self animeInfoWithId:imdbId success:success failure:failure]; break; } default: break; } } - (void)searchForShowWithType:(PTItemType)type name:(NSString *)name success:(PTAPIManagerSuccessItems)success failure:(PTAPIManagerFailure)failure { switch (type) { case PTItemTypeMovie: { [self searchForMovieWithName:name success:success failure:failure]; break; } case PTItemTypeShow: { [self searchForTVSeriesWithName:name success:success failure:failure]; break; } case PTItemTypeAnime: { [self searchForAnimeWithName:name success:success failure:failure]; break; } default: break; } } - (void)topShowsWithType:(PTItemType)type withPage:(NSUInteger)page success:(PTAPIManagerSuccessItems)success failure:(PTAPIManagerFailure)failure{ switch (type) { case PTItemTypeMovie: { [self topMoviesWithPage:page success:success failure:failure]; break; } case PTItemTypeShow: { [self topTVSeriesWithPage:page success:success failure:failure]; break; } case PTItemTypeAnime: { [self topAnimeWithPage:page success:success failure:failure]; break; } default: break; } } #pragma mark - Private Methods - (void)dataFromURL:(NSURL *)URL success:(void(^)(id JSONObject))success failure:(PTAPIManagerFailure)failure { return [self dataFromURL:URL HTTPheaders:nil success:success failure:failure]; } - (void)dataFromURL:(NSURL *)URL HTTPheaders:(NSDictionary *)HTTPheaders success:(void(^)(id JSONObject))success failure:(PTAPIManagerFailure)failure { [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; if (HTTPheaders) { for (NSString *key in HTTPheaders.allKeys) { [request addValue:HTTPheaders[key] forHTTPHeaderField:key]; } } [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; void (^handleError)(NSError *) = ^(NSError *error) { if (error) { NSLog(@"%@", error); if (failure) { failure(error); } } }; if (error) { handleError(error); return; } NSError *JSONError; id JSONObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&JSONError]; if (JSONError) { handleError(JSONError); return; } if (success) { success(JSONObject); }; }); }] resume]; } #pragma mark Movies - (void)topMoviesWithPage:(NSUInteger)page success:(PTAPIManagerSuccessItems)success failure:(PTAPIManagerFailure)failure { NSString *path = [NSString stringWithFormat:@"list?" "page=%ld&limit=%ld&order_by=desc&sort_by=seeds&with_rt_ratings=true", (long)page + 1, (long)PTAPIManagerResultsLimit]; NSString *URLString = [PTAPIManagerMoviesEndPoint stringByAppendingPathComponent:path]; [self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) { if (success) { NSArray *items = [((NSDictionary *)JSONObject) objectForKey:@"MovieList"]; success(items); } } failure:failure]; } - (void)movieInfoWithId:(NSString *)imdbId success:(PTAPIManagerSuccessItem)success failure:(PTAPIManagerFailure)failure { NSString *path = [NSString stringWithFormat:@"list?" "page=1&limit=5&order_by=desc&sort_by=seeds&with_rt_ratings=true"]; // NSString *path = [NSString stringWithFormat:@"list?id=%@", imdbId]; NSString *URLString = [PTAPIManagerMoviesEndPoint stringByAppendingPathComponent:path]; [self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) { if (success) { NSDictionary *items = [((NSDictionary *)JSONObject) objectForKey:@"MovieList"]; for(id key in items) { NSString *id = [key objectForKey:@"id"]; if([id isEqualToString:imdbId]) { success(key); break; } } // success(items); } } failure:failure]; } - (void)searchForMovieWithName:(NSString *)name success:(PTAPIManagerSuccessItems)success failure:(PTAPIManagerFailure)failure { NSString *path = [[NSString stringWithFormat:@"list?sort_by=seeds&limit=%ld&with_rt_ratings=true&page=1&keywords=%@", (long)PTAPIManagerResultsLimit, name] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSString *URLString = [PTAPIManagerMoviesEndPoint stringByAppendingPathComponent:path]; [self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) { if (success) { NSArray *items = [((NSDictionary *)JSONObject) objectForKey:@"MovieList"]; success(items); } } failure:failure]; } #pragma mark TVSeries - (void)topTVSeriesWithPage:(NSUInteger)page success:(PTAPIManagerSuccessItems)success failure:(PTAPIManagerFailure)failure { NSString *path = [NSString stringWithFormat:@"shows/%ld?limit=%lu", (long)page + 1, (long)PTAPIManagerResultsLimit]; NSString *URLString = [PTAPIManagerShowsEndPoint stringByAppendingPathComponent:path]; [self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) { if (success) { success((NSArray *)JSONObject); } } failure:failure]; } - (void)tvSeriesInfoWithId:(NSString *)imdbId success:(PTAPIManagerSuccessItem)success failure:(PTAPIManagerFailure)failure { NSString *path = [NSString stringWithFormat:@"show/%@", imdbId]; NSString *URLString = [PTAPIManagerShowsEndPoint stringByAppendingPathComponent:path]; [self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) { if (success) { success((NSDictionary *)JSONObject); } } failure:failure]; } - (void)searchForTVSeriesWithName:(NSString *)name success:(PTAPIManagerSuccessItems)success failure:(PTAPIManagerFailure)failure { NSString *path = [[NSString stringWithFormat:@"shows/1?limit=%ld&keywords=%@&sort=seeds", (long)PTAPIManagerResultsLimit, name] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSString *URLString = [PTAPIManagerShowsEndPoint stringByAppendingPathComponent:path]; [self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) { if (success) { success((NSArray *)JSONObject); } } failure:failure]; } #pragma mark Anime - (void)topAnimeWithPage:(NSUInteger)page success:(PTAPIManagerSuccessItems)success failure:(PTAPIManagerFailure)failure { NSString *path = [[NSString stringWithFormat:@"list.php?page=%ld&limit=%ld&sort=popularity&type=All", (long)page, (long)PTAPIManagerResultsLimit] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSString *URLString = [PTAPIManagerAnimeEndPoint stringByAppendingPathComponent:path]; [self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) { if (success) { success((NSArray *)JSONObject); } } failure:failure]; } - (void)animeInfoWithId:(NSString *)imdbId success:(PTAPIManagerSuccessItem)success failure:(PTAPIManagerFailure)failure { NSString *path = [NSString stringWithFormat:@"anime.php?id=%@", imdbId]; NSString *URLString = [PTAPIManagerAnimeEndPoint stringByAppendingPathComponent:path]; [self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) { if (success) { success((NSDictionary *)JSONObject); } } failure:failure]; } - (void)searchForAnimeWithName:(NSString *)name success:(PTAPIManagerSuccessItems)success failure:(PTAPIManagerFailure)failure { NSString *path = [[NSString stringWithFormat:@"/list.php?search=%@&limit=%ld&sort=popularity&type=All", [name stringByReplacingOccurrencesOfString:@" " withString:@"+"], (long)PTAPIManagerResultsLimit] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSString *URLString = [PTAPIManagerAnimeEndPoint stringByAppendingPathComponent:path]; [self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) { if (success) { success((NSArray *)JSONObject); } } failure:failure]; } #pragma mark - Trakt.tv /* NSString *const PTAPIManagerTrakttvAccessTokenKey = @"TrakttvAccessToken"; NSString *const PTAPIManagerTrakttvAPIEndPoint = @"http://api.trakt.tv"; NSString *const PTAPIManagerTrakttvAPIKey = @"df8d400233727be104e5caf40e07d785b6963c0e194dcbd24f806e8a4e243167"; NSString *const PTAPIManagerTrakttvAPIVersion = @"2"; NSString *const PTAPIManagerTrakttvClientId = @"df8d400233727be104e5caf40e07d785b6963c0e194dcbd24f806e8a4e243167"; NSString *const PTAPIManagerTrakttvClientSecret = @"1a98885c5271f7162ac51b2c1dd09decc55df127f5e1b29af533d35eee5df9b2"; NSString *const PTAPIManagerTrakttvRedirectURL = @"urn:ietf:wg:oauth:2.0:oob"; + (NSString *)trakttvAccessToken { return [[NSUserDefaults standardUserDefaults] objectForKey:PTAPIManagerTrakttvAccessTokenKey]; } + (void)updateTrakttvAccessToken:(NSString *)accessToken { [[NSUserDefaults standardUserDefaults] setObject:accessToken forKey:PTAPIManagerTrakttvAccessTokenKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } + (NSURL *)trakttvAuthorizationURL { NSString *authPath = [NSString stringWithFormat:@"http://trakt.tv/oauth/authorize?client_id=%@&redirect_uri=%@&response_type=code", PTAPIManagerTrakttvClientId, PTAPIManagerTrakttvRedirectURL]; return [NSURL URLWithString:authPath]; } - (void)trakttvPerformRequestWithAPIMethod:(NSString *)APIMethod HTTPMethod:(NSString *)HTTPMethod HTTPBodyPayload:(NSDictionary *)HTTPBodyPayload OAuthRequired:(BOOL)OAuthRequired success:(void(^)(id JSONObject))success failure:(PTAPIManagerFailure)failure { NSString *path = [PTAPIManagerTrakttvAPIEndPoint stringByAppendingPathComponent:APIMethod]; NSURL *URL = [NSURL URLWithString:path]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; request.HTTPMethod = HTTPMethod; // Configure headers [request addValue:@"application/json" forHTTPHeaderField:@"Content-type"]; [request addValue:PTAPIManagerTrakttvAPIKey forHTTPHeaderField:@"trakt-api-key"]; [request addValue:PTAPIManagerTrakttvAPIVersion forHTTPHeaderField:@"trakt-api-version"]; if (OAuthRequired) { NSString *bearerToken = [NSString stringWithFormat:@"Bearer [%@]", [PTAPIManager trakttvAccessToken]]; [request addValue:bearerToken forHTTPHeaderField:@"Authorization"]; } void (^handleError)(NSError *) = ^(NSError *error) { if (error) { NSLog(@"%@", error); if (failure) { failure(error); } } }; // Configure body NSError *JSONError; NSData *JSONBody = [NSJSONSerialization dataWithJSONObject:HTTPBodyPayload options:0 error:&JSONError]; handleError(JSONError); request.HTTPBody = JSONBody; [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ if (error) { handleError(error); return; } NSError *JSONError; id JSONObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&JSONError]; if (JSONError) { handleError(JSONError); return; } if (success) { success(JSONObject); } }); }] resume]; } - (void)accessTokenWithAuthorizationCode:(NSString *)authorizationCode success:(void(^)(NSString *accessToken))success failure:(PTAPIManagerFailure)failure { NSString *APIMethod = @"oauth/token"; NSDictionary *HTTPBodyPayload = @{@"code": authorizationCode, @"client_id": PTAPIManagerTrakttvClientId, @"client_secret": PTAPIManagerTrakttvClientSecret, @"redirect_uri": PTAPIManagerTrakttvRedirectURL, @"grant_type": @"authorization_code"}; [self trakttvPerformRequestWithAPIMethod:APIMethod HTTPMethod:@"POST" HTTPBodyPayload:HTTPBodyPayload OAuthRequired:NO success:^(id JSONObject) { if (success) { success([((NSDictionary *)JSONObject) objectForKey:@"access_token"]); } } failure:failure]; } - (void)createListWithName:(NSString *)name success:(PTAPIManagerSuccessNone)success failure:(PTAPIManagerFailure)failure { NSString *APIMethod = @"users/me/lists"; NSDictionary *HTTPBodyPayload = @{@"name": @"ololo", @"description": @"ololo", @"privacy": @"private", @"display_numbers": @"false", @"allow_comments": @"true"}; [self trakttvPerformRequestWithAPIMethod:APIMethod HTTPMethod:@"POST" HTTPBodyPayload:HTTPBodyPayload OAuthRequired:YES success:nil failure:failure]; } */ @end ================================================ FILE: PopcornTime/Models/PTTorrentStreamer.h ================================================ // // PTTorrentStreamer.h // PopcornTime // // Created by Danylo Kostyshyn on 2/23/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // #import typedef struct { float bufferingProgress; float totalProgreess; int downloadSpeed; int upoadSpeed; int seeds; int peers; } PTTorrentStatus; typedef void (^PTTorrentStreamerProgress)(PTTorrentStatus status); typedef void (^PTTorrentStreamerReadyToPlay)(NSURL *videoFileURL); typedef void (^PTTorrentStreamerFailure)(NSError *error); @interface PTTorrentStreamer : NSObject + (instancetype)sharedStreamer; - (void)startStreamingFromFileOrMagnetLink:(NSString *)filePathOrMagnetLink progress:(PTTorrentStreamerProgress)progreess readyToPlay:(PTTorrentStreamerReadyToPlay)readyToPlay failure:(PTTorrentStreamerFailure)failure; - (void)cancelStreaming; @end ================================================ FILE: PopcornTime/Models/PTTorrentStreamer.mm ================================================ // // PTTorrentStreamer.m // PopcornTime // // Created by Danylo Kostyshyn on 2/23/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // #import "PTTorrentStreamer.h" #import #import #import #import #import #import using namespace libtorrent; @interface PTTorrentStreamer() @property (nonatomic, strong) dispatch_queue_t alertsQueue; @property (nonatomic, getter=isAlertsLoopActive) BOOL alertsLoopActive; @property (nonatomic, strong) NSString *savePath; @property (nonatomic, getter=isDownloading) BOOL downloading; @property (nonatomic, getter=isStreaming) BOOL streaming; @property (nonatomic, copy) PTTorrentStreamerProgress progressBlock; @property (nonatomic, copy) PTTorrentStreamerReadyToPlay readyToPlayBlock; @property (nonatomic, copy) PTTorrentStreamerFailure failureBlock; @end @implementation PTTorrentStreamer { session *_session; std::vector required_pieces; } + (instancetype)sharedStreamer { static dispatch_once_t onceToken; static PTTorrentStreamer *sharedStreamer; dispatch_once(&onceToken, ^{ sharedStreamer = [[PTTorrentStreamer alloc] init]; }); return sharedStreamer; } - (instancetype)init { self = [super init]; if (self) { [self setupSession]; } return self; } #pragma mark - + (NSString *)downloadsDirectory { NSString *downloadsDirectoryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"Downloads"]; if (![[NSFileManager defaultManager] fileExistsAtPath:downloadsDirectoryPath]) { NSError *error; [[NSFileManager defaultManager] createDirectoryAtPath:downloadsDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error]; if (error) { NSLog(@"%@", error); return nil; } } return downloadsDirectoryPath; } - (void)setupSession { // _session = new session(fingerprint("PopcornTime", 1, 0, 0, 0), // std::make_pair(6881, 6889), // 0, // session::start_default_features | session::add_default_plugins, // alert::all_categories); error_code ec; _session = new session(); _session->set_alert_mask(alert::all_categories); _session->listen_on(std::make_pair(6881, 6889), ec); if (ec) { NSLog(@"failed to open listen socket: %s", ec.message().c_str()); } session_settings settings = _session->settings(); settings.announce_to_all_tiers = true; settings.announce_to_all_trackers = true; settings.prefer_udp_trackers = false; settings.max_peerlist_size = 0; _session->set_settings(settings); } - (void)startStreamingFromFileOrMagnetLink:(NSString *)filePathOrMagnetLink progress:(PTTorrentStreamerProgress)progreess readyToPlay:(PTTorrentStreamerReadyToPlay)readyToPlay failure:(PTTorrentStreamerFailure)failure; { self.progressBlock = progreess; self.readyToPlayBlock = readyToPlay; self.failureBlock = failure; self.alertsQueue = dispatch_queue_create("com.popcorntime.ios.torrentstreamer.alerts", DISPATCH_QUEUE_SERIAL); self.alertsLoopActive = YES; dispatch_async(self.alertsQueue, ^{ [self alertsLoop]; }); error_code ec; add_torrent_params tp; NSString *MD5String = nil; if ([filePathOrMagnetLink hasPrefix:@"magnet"]) { NSString *magnetLink = filePathOrMagnetLink; magnetLink = [magnetLink stringByAppendingString:@"&tr=udp://open.demonii.com:1337" "&tr=udp://tracker.coppersurfer.tk:6969"]; tp.url = std::string([magnetLink UTF8String]); MD5String = [CocoaSecurity md5:magnetLink].hexLower; } else { NSString *filePath = filePathOrMagnetLink; if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { NSData *fileData = [NSData dataWithContentsOfFile:filePath]; MD5String = [CocoaSecurity md5WithData:fileData].hexLower; tp.ti = new torrent_info([filePathOrMagnetLink UTF8String], ec); if (ec) { NSLog(@"%s", ec.message().c_str()); return; } } else { NSLog(@"File doesn't exists at path: %@", filePath); return; } } NSString *halfMD5String = [MD5String substringToIndex:16]; self.savePath = [[PTTorrentStreamer downloadsDirectory] stringByAppendingPathComponent:halfMD5String]; NSError *error; [[NSFileManager defaultManager] createDirectoryAtPath:self.savePath withIntermediateDirectories:YES attributes:nil error:&error]; if (error) { NSLog(@"Can't create directory at path: %@", self.savePath); return; } tp.save_path = std::string([self.savePath UTF8String]); tp.storage_mode = storage_mode_allocate; torrent_handle th = _session->add_torrent(tp, ec); th.set_sequential_download(true); if (ec) { NSLog(@"%s", ec.message().c_str()); return; } self.downloading = YES; [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; } - (void)cancelStreaming { if ([self isDownloading]) { self.alertsQueue = nil; self.alertsLoopActive = NO; std::vector ths = _session->get_torrents(); for(std::vector::size_type i = 0; i != ths.size(); i++) { _session->remove_torrent(ths[i], session::delete_files); } required_pieces.clear(); self.progressBlock = nil; self.readyToPlayBlock = nil; self.failureBlock = nil; NSError *error; [[NSFileManager defaultManager] removeItemAtPath:self.savePath error:&error]; if (error) NSLog(@"%@", error); self.savePath = nil; self.streaming = NO; self.downloading = NO; [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; } } #pragma mark - Alerts Loop #define ALERTS_LOOP_WAIT_MILLIS 500 #define MIN_PIECES 15 #define PIECE_DEADLINE_MILLIS 100 #define LIBTORRENT_PRIORITY_SKIP 0 #define LIBTORRENT_PRIORITY_MAXIMUM 7 - (void)alertsLoop { std::deque deque; time_duration max_wait = milliseconds(ALERTS_LOOP_WAIT_MILLIS); while ([self isAlertsLoopActive]) { const alert *ptr = _session->wait_for_alert(max_wait); if (ptr != nullptr) { _session->pop_alerts(&deque); for (std::deque::iterator it=deque.begin(); it != deque.end(); ++it) { std::unique_ptr alert(*it); // NSLog(@"type:%d msg:%s", alert->type(), alert->message().c_str()); switch (alert->type()) { case metadata_received_alert::alert_type: [self metadataReceivedAlert:(metadata_received_alert *)alert.get()]; break; case block_finished_alert::alert_type: [self pieceFinishedAlert:(piece_finished_alert *)alert.get()]; break; // In case the video file is already fully downloaded case torrent_finished_alert::alert_type: [self torrentFinishedAlert:(torrent_finished_alert *)alert.get()]; break; default: break; } } deque.clear(); } } } - (void)prioritizeNextPieces:(torrent_handle)th { int next_required_piece = required_pieces[MIN_PIECES-1]+1; required_pieces.clear(); boost::intrusive_ptr ti = th.torrent_file(); for (int i=next_required_piece; inum_pieces()) { th.piece_priority(i, LIBTORRENT_PRIORITY_MAXIMUM); th.set_piece_deadline(i, PIECE_DEADLINE_MILLIS, torrent_handle::alert_when_available); required_pieces.push_back(i); } } } - (void)processTorrent:(torrent_handle)th { if (![self isStreaming]) { self.streaming = YES; if (self.readyToPlayBlock) { boost::intrusive_ptr ti = th.torrent_file(); int file_index = [self indexOfLargestFileInTorrent:th]; file_entry fe = ti->file_at(file_index); std::string path = fe.path; NSString *fileName = [NSString stringWithCString:path.c_str() encoding:NSUTF8StringEncoding]; NSURL *fileURL = [NSURL fileURLWithPath:[self.savePath stringByAppendingPathComponent:fileName]]; dispatch_async(dispatch_get_main_queue(), ^{ self.readyToPlayBlock(fileURL); }); } } } - (int)indexOfLargestFileInTorrent:(torrent_handle)th { boost::intrusive_ptr ti = th.torrent_file(); int files_count = ti->num_files(); if (files_count > 1) { size_type largest_size = -1; int largest_file_index = -1; for (int i=0; ifile_at(i); if (fe.size > largest_size) { largest_size = fe.size; largest_file_index = i; } } return largest_file_index; } return 0; } #pragma mark - Logging - (void)logPiecesStatus:(torrent_handle)th { NSString *pieceStatus = @""; boost::intrusive_ptr ti = th.torrent_file(); for(std::vector::size_type i=0; i!=required_pieces.size(); i++) { int piece = required_pieces[i]; pieceStatus = [pieceStatus stringByAppendingFormat:@"%d:%d ", piece, th.have_piece(piece)]; } NSLog(@"%@", pieceStatus); } - (void)logTorrentStatus:(PTTorrentStatus)status { NSString *speedString = [NSByteCountFormatter stringFromByteCount:status.downloadSpeed countStyle:NSByteCountFormatterCountStyleBinary]; NSLog(@"%.0f%%, %.0f%%, %@/s, %d, %d", status.bufferingProgress*100, status.totalProgreess*100, speedString, status.seeds, status.peers); } #pragma mark - Alerts - (void)metadataReceivedAlert:(metadata_received_alert *)alert { torrent_handle th = alert->handle; int file_index = [self indexOfLargestFileInTorrent:th]; std::vector file_priorities = th.file_priorities(); std::fill(file_priorities.begin(), file_priorities.end(), LIBTORRENT_PRIORITY_SKIP); file_priorities[file_index] = LIBTORRENT_PRIORITY_MAXIMUM; th.prioritize_files(file_priorities); boost::intrusive_ptr ti = th.torrent_file(); int first_piece = ti->map_file(file_index, 0, 0).piece; for (int i=first_piece; ifile_at(file_index).size; int last_piece = ti->map_file(file_index, file_size-1, 0).piece; required_pieces.push_back(last_piece); for (int i=1; i<10; i++) { required_pieces.push_back(last_piece-i); } for(std::vector::size_type i=0; i!=required_pieces.size(); i++) { int piece = required_pieces[i]; th.piece_priority(piece, LIBTORRENT_PRIORITY_MAXIMUM); th.set_piece_deadline(piece, PIECE_DEADLINE_MILLIS, torrent_handle::alert_when_available); } } - (void)pieceFinishedAlert:(piece_finished_alert *)alert { torrent_handle th = alert->handle; torrent_status status = th.status(); int requiredPiecesDownloaded = 0; BOOL allRequiredPiecesDownloaded = YES; for(std::vector::size_type i=0; i!=required_pieces.size(); i++) { int piece = required_pieces[i]; if (th.have_piece(piece)) { requiredPiecesDownloaded++; } else { allRequiredPiecesDownloaded = NO; } } [self logPiecesStatus:th]; int requiredPieces = (int)required_pieces.size(); float bufferingProgress = 1.0 - (requiredPieces-requiredPiecesDownloaded)/(float)requiredPieces; PTTorrentStatus torrentStatus = {bufferingProgress, status.progress, status.download_rate, status.upload_rate, status.num_seeds, status.num_peers}; [self logTorrentStatus:torrentStatus]; if (self.progressBlock) { dispatch_async(dispatch_get_main_queue(), ^{ self.progressBlock(torrentStatus); }); } if (allRequiredPiecesDownloaded) { [self prioritizeNextPieces:th]; [self processTorrent:th]; } } - (void)torrentFinishedAlert:(torrent_finished_alert *)alert { [self processTorrent:alert->handle]; } @end ================================================ FILE: PopcornTime/Models/ParseManager.swift ================================================ // // ParseManager.swift // // // Created by Andriy K. on 6/23/15. // // import UIKit class ParseShowData: NSObject { private var collection = [String : PFObject]() convenience init(episodesFromParse: [PFObject]) { self.init() for episode in episodesFromParse { let seasonIndex = episode.objectForKey(ParseManager.sharedInstance.seasonKey) as! Int let episodeIndex = episode.objectForKey(ParseManager.sharedInstance.episodeKey) as! Int let key = dictKey(seasonIndex, episode: episodeIndex) collection[key] = episode } } func isEpisodeWatched(season: Int, episode: Int) -> Bool { let key = dictKey(season, episode: episode) if let episode = collection[key] { if let isWatched = episode.objectForKey(ParseManager.sharedInstance.watchedKey) as? Bool { return isWatched } } return false } private func dictKey(season: Int, episode: Int) -> String { return "\(season)_\(episode)" } } class ParseManager: NSObject { static let sharedInstance = ParseManager() let showClassName = "Show" let episodeClassName = "Episode" let showIdKey = "showId" let userKey = "user" let titleKey = "title" let seasonKey = "season" let episodeKey = "episodeNumber" let showKey = "show" let watchedKey = "watched" private override init() { } // MARK: - Public API var user: PFUser? { return PFUser.currentUser() } func markEpisode(episodeInfo: Episode, basicInfo: BasicInfo) { if let user = user { let query = PFQuery(className:showClassName) query.whereKey(userKey, equalTo:user) query.whereKey(showIdKey, equalTo:basicInfo.identifier) query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in var show: PFObject if let object = objects?.first as PFObject? { show = object } else { show = PFObject(className: self.showClassName) show.setObject(basicInfo.identifier, forKey: self.showIdKey) if let title = basicInfo.title { show.setObject(title, forKey: self.titleKey) } let relation = show.relationForKey(self.userKey) relation.addObject(user) } show.saveInBackgroundWithBlock({ (success, error) -> Void in let queryEpisode = PFQuery(className:self.episodeClassName) queryEpisode.whereKey(self.seasonKey, equalTo:episodeInfo.seasonNumber) queryEpisode.whereKey(self.episodeKey, equalTo:episodeInfo.episodeNumber) queryEpisode.whereKey(self.showKey, equalTo: show) queryEpisode.findObjectsInBackgroundWithBlock { (episodes, episodeError) -> Void in var episode: PFObject if let ep = episodes?.first as PFObject? { episode = ep } else { let newEpisode = PFObject(className: self.episodeClassName) let relationShow = newEpisode.relationForKey(self.showKey) relationShow.addObject(show) newEpisode.setObject(episodeInfo.seasonNumber, forKey: self.seasonKey) newEpisode.setObject(episodeInfo.episodeNumber, forKey: self.episodeKey) episode = newEpisode } episode.setObject(true, forKey: self.watchedKey) episode.saveInBackgroundWithBlock(nil) } }) } } } /// Mark [Episode] as watched on Parse. func markEpisodes(episodesInfo: [Episode], basicInfo: BasicInfo, completionHandler: PFBooleanResultBlock?) { if episodesInfo.count == 0 { return } let episodeNumbers = episodesInfo.map(){ episode in return episode.episodeNumber } let seasonNumber = episodesInfo.first!.seasonNumber if let user = user { let query = PFQuery(className:showClassName) query.whereKey(userKey, equalTo:user) query.whereKey(showIdKey, equalTo:basicInfo.identifier) query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in var show: PFObject if let object = objects?.first as PFObject? { show = object } else { show = PFObject(className: self.showClassName) show.setObject(basicInfo.identifier, forKey: self.showIdKey) if let title = basicInfo.title { show.setObject(title, forKey: self.titleKey) } let relation = show.relationForKey(self.userKey) relation.addObject(user) } show.saveInBackgroundWithBlock({ (success, error) -> Void in let queryEpisode = PFQuery(className:self.episodeClassName) queryEpisode.whereKey(self.seasonKey, equalTo: seasonNumber) queryEpisode.whereKey(self.episodeKey, containedIn: episodeNumbers) queryEpisode.whereKey(self.showKey, equalTo: show) queryEpisode.findObjectsInBackgroundWithBlock { (results, episodeError) -> Void in var marked = [UInt]() var pfObjects = [PFObject]() if let parseEpisodes = results as [PFObject]? { print("\(parseEpisodes.count): episodes already on Parse") for parseEp in parseEpisodes { parseEp.setObject(true, forKey: self.watchedKey) if let parseEpNumber = parseEp.objectForKey(self.episodeKey) as? UInt { marked.append(parseEpNumber) pfObjects.append(parseEp) print("parse ep:\(parseEpNumber) marked") } } } for ep in episodesInfo { if marked.contains(ep.episodeNumber) == false { let newEpisode = PFObject(className: self.episodeClassName) let relationShow = newEpisode.relationForKey(self.showKey) relationShow.addObject(show) newEpisode.setObject(ep.seasonNumber, forKey: self.seasonKey) newEpisode.setObject(ep.episodeNumber, forKey: self.episodeKey) newEpisode.setObject(true, forKey: self.watchedKey) marked.append(ep.episodeNumber) pfObjects.append(newEpisode) print("ep:\(ep.episodeNumber) marked") } } PFObject.saveAllInBackground(pfObjects, block: completionHandler) } }) } } } func parseEpisodesData(basicInfo: BasicInfo, handler: (ParseShowData) -> Void) { if let user = user { let query = PFQuery(className: showClassName) query.whereKey(userKey, equalTo: user) query.whereKey(showIdKey, equalTo:basicInfo.identifier) query.findObjectsInBackgroundWithBlock({ (results, error) -> Void in if let show = results?.first as PFObject? { let queryEpisode = PFQuery(className: self.episodeClassName) queryEpisode.whereKey(self.showKey, equalTo: show) do { let episodes = try queryEpisode.findObjects() as [PFObject] let parserData = ParseShowData(episodesFromParse: episodes) handler(parserData) } catch let error as NSError { print(error) } } }) } } } ================================================ FILE: PopcornTime/Models/Show.swift ================================================ // // Show.swift // PopcornTime // // Created by Danylo Kostyshyn on 3/19/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import Foundation class Show: BasicInfo { var seasons = [Season]() func thumbnail(_ original: String) -> String { return original.replacingOccurrences(of: "original", with: "thumb", options: NSString.CompareOptions.caseInsensitive, range: nil) } required init(dictionary: [AnyHashable: Any]) { super.init(dictionary: dictionary) identifier = dictionary["imdb_id"] as! String title = dictionary["title"] as? String year = dictionary["year"] as? String if let imagesDict = dictionary["images"] as? NSDictionary { images = [Image]() if let banner = imagesDict["banner"] as? String { let URL = Foundation.URL(string: thumbnail(banner)) let image = Image(URL: URL!, type: .banner) images.append(image) } if let fanart = imagesDict["fanart"] as? String { let URL = Foundation.URL(string: thumbnail(fanart)) let image = Image(URL: URL!, type: .fanart) images.append(image) } if let poster = imagesDict["poster"] as? String { let URL = Foundation.URL(string: thumbnail(poster)) let image = Image(URL: URL!, type: .poster) images.append(image) } smallImage = images.filter({$0.type == ImageType.poster}).first bigImage = images.filter({$0.type == ImageType.fanart}).first } } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! } override func update(_ dictionary: [AnyHashable: Any]) { synopsis = dictionary["synopsis"] as? String seasons.removeAll(keepingCapacity: true) var allEpisodes = [Episode]() var allSeasonsNumbers = [UInt:Bool]() guard let episodesDicts = dictionary["episodes"] as? [[AnyHashable: Any]] else { return } for episodeDict in episodesDicts { var videos = [Video]() if let torrents = episodeDict["torrents"] as? [String : NSDictionary] { for torrent in torrents { let quality = torrent.0 if quality == "0" { continue } let url = torrent.1["url"] as! String let video = Video(name: nil, quality: quality, size: 0, duration: 0, subGroup: nil, magnetLink: url) videos.append(video) } } videos = videos.sorted(by: { (a, b) -> Bool in var aQuality: Int = 0 Scanner(string: a.quality!).scanInt(&aQuality) var bQuality: Int = 0 Scanner(string: b.quality!).scanInt(&bQuality) return aQuality < bQuality }) let seasonNumber = (episodeDict["season"] as! UInt) if (allSeasonsNumbers[seasonNumber] == nil) { allSeasonsNumbers[seasonNumber] = true } let title = episodeDict["title"] as? String let episodeNumber = (episodeDict["episode"] as! UInt) let synopsis = episodeDict["overview"] as? String let episode = Episode(title: title, desc: synopsis, seasonNumber: seasonNumber, episodeNumber: episodeNumber, videos: videos) allEpisodes.append(episode) } var seasonsNumbers = Array(allSeasonsNumbers.keys) seasonsNumbers.sort(by: { (a, b) -> Bool in return a < b }) for seasonNumber in seasonsNumbers { let seasonEpisodes = allEpisodes.filter({ (episode) -> Bool in return episode.seasonNumber == seasonNumber }) if seasonEpisodes.count > 0{ let season = Season(seasonNumber: seasonNumber, episodes: seasonEpisodes) seasons.append(season) } } } } extension Show: ContainsEpisodes { func episodeFor(seasonIndex: Int, episodeIndex: Int) -> Episode { let episode = seasons[seasonIndex].episodes[episodeIndex] return episode } func episodesFor(seasonIndex: Int) -> [Episode] { return seasons[seasonIndex].episodes } } ================================================ FILE: PopcornTime/PopcornTime-Bridging-Header.h ================================================ // // Use this file to import your target's public headers that you would like to expose to Swift. // #import "PTAPIManager.h" #import "PTTorrentStreamer.h" #import "VDLPlaybackViewController.h" #import #import #import ================================================ FILE: PopcornTime/Resources/Images.xcassets/AnimeIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "anime.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: PopcornTime/Resources/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "AppIcon60@2x.png", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "AppIcon76.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "AppIcon76@2x.png", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: PopcornTime/Resources/Images.xcassets/BigLogo.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "popcorn-time-logo.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: PopcornTime/Resources/Images.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: PopcornTime/Resources/Images.xcassets/SubwayIconSet/AddToFavoritesIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "icon_087@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: PopcornTime/Resources/Images.xcassets/SubwayIconSet/FavoritesIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "icon_086@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: PopcornTime/Resources/Images.xcassets/SubwayIconSet/MoviesIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "icon_0281@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: PopcornTime/Resources/Images.xcassets/SubwayIconSet/RemoveFromFavoritesIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "icon_088@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: PopcornTime/Resources/Images.xcassets/SubwayIconSet/SettingsIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "icon_0186@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: PopcornTime/Resources/Images.xcassets/SubwayIconSet/ShowsIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "icon_0304@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: PopcornTime/Resources/Launch Screen.xib ================================================ ================================================ FILE: PopcornTime/Resources/Main.storyboard ================================================ ================================================ FILE: PopcornTime/Views/EpisodeCell.swift ================================================ // // EpisodeCell.swift // PopcornTime // // Created by Andrew K. on 3/13/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class EpisodeCell: UICollectionViewCell { let watchedAlpha:CGFloat = 0.5 let defaultAlpha:CGFloat = 1.0 @IBOutlet weak var titleLabel: UILabel! var watchedEpisode = false { didSet { if watchedEpisode { alpha = watchedAlpha } else { alpha = defaultAlpha } } } } ================================================ FILE: PopcornTime/Views/EpisodeCell.xib ================================================ ================================================ FILE: PopcornTime/Views/MoreShowsCollectionViewCell.swift ================================================ // // MoreShowsCollectionViewCell.swift // PopcornTime // // Created by Andrew K. on 3/10/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class MoreShowsCollectionViewCell: UICollectionViewCell { override func awakeFromNib() { super.awakeFromNib() // Initialization code } } ================================================ FILE: PopcornTime/Views/MoreShowsCollectionViewCell.xib ================================================ ================================================ FILE: PopcornTime/Views/SeasonHeader.swift ================================================ // // SeasonHeader.swift // PopcornTime // // Created by Andrew K. on 4/14/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class SeasonHeader: UICollectionReusableView { @IBOutlet weak var container: UIView! @IBOutlet weak var titleLabel: UILabel! } ================================================ FILE: PopcornTime/Views/SeasonHeader.xib ================================================ ================================================ FILE: PopcornTime/Views/ShowCollectionViewCell.swift ================================================ // // ShowCollectionViewCell.swift // PopcornTime // // Created by Andrew K. on 3/8/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class ShowCollectionViewCell: UICollectionViewCell { @IBOutlet weak fileprivate var imageView: UIImageView! @IBOutlet weak fileprivate var titleLabel: UILabel! var image: UIImage? { didSet { self.imageView?.image = image self.titleLabel.isHidden = image != nil } } var title: String? { didSet { titleLabel.text = title } } override func prepareForReuse() { super.prepareForReuse() self.image = nil } } ================================================ FILE: PopcornTime/Views/ShowCollectionViewCell.xib ================================================ ================================================ FILE: PopcornTime/Views/StratchyHeader.swift ================================================ // // StratchyHeader.swift // PopcornTime // // Created by Andrew K. on 4/6/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit protocol StratchyHeaderDelegate: class { ///Triggers when header max stratch value is recalculated func stratchyHeader(_ header: StratchyHeader, didResetMaxStratchValue value: CGFloat) } class StratchyHeader: UICollectionReusableView { weak var delegate: StratchyHeaderDelegate? // MARK: - Public API var image: UIImage? { didSet { if let image = image { imageAspectRatio = image.size.height / image.size.width backgroundImageView.image = image updateImageViewConstraints() } } } var headerSize: CGSize = CGSize(width: 1, height: 1) { didSet { updateImageViewConstraints() } } // MARK: - UICollectionReusableView override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) { super.apply(layoutAttributes) let attributes = layoutAttributes as! StratchyLayoutAttributes let height = attributes.frame.height if (previousHeight != height) { if (maxStratch != 0) { let alpha = 1 - attributes.deltaY / maxStratch foregroundView.alpha = alpha } if (imageAspectRatio != 0) { heightConstraint.constant = imageViewActualHeight - attributes.deltaY widthConstraint.constant = imageViewActualWidth - (attributes.deltaY / imageAspectRatio) } previousHeight = height } } // MARK: - Private @IBOutlet weak fileprivate var widthConstraint: NSLayoutConstraint! @IBOutlet weak fileprivate var heightConstraint: NSLayoutConstraint! @IBOutlet weak fileprivate var backgroundImageView: UIImageView! @IBOutlet weak var foregroundView: UIView! @IBOutlet weak var foregroundImage: UIImageView! @IBOutlet weak var synopsisTextView: UILabel! fileprivate var maxStratch: CGFloat = 0 fileprivate var zoomWidthCoef: CGFloat { get { let headerAspectRatio = headerSize.height / headerSize.width return (1.7 * headerAspectRatio) / 0.5325 // Experimentally calculated value :] } } fileprivate var imageAspectRatio: CGFloat = 0 fileprivate var imageViewActualWidth: CGFloat { return headerSize.width * zoomWidthCoef } fileprivate var imageViewActualHeight: CGFloat { return imageViewActualWidth * imageAspectRatio } fileprivate var previousHeight: CGFloat = 0 fileprivate func updateImageViewConstraints() { // let dX = fabs(headerSize.height - imageViewActualHeight)/2 let dY = fabs(headerSize.width - imageViewActualWidth)/2 maxStratch = dY//max(dX, dY) self.delegate?.stratchyHeader(self, didResetMaxStratchValue: maxStratch) widthConstraint.constant = imageViewActualWidth heightConstraint.constant = imageViewActualHeight } } ================================================ FILE: PopcornTime/Views/StratchyHeader.xib ================================================ ================================================ FILE: PopcornTime/Views/StratchyHeaderLayout.swift ================================================ // // StratchyHeaderLayout.swift // PopcornTime // // Created by Andrew K. on 3/13/15. // Copyright (c) 2015 PopcornTime. All rights reserved. // import UIKit class StratchyLayoutAttributes: UICollectionViewLayoutAttributes { var deltaY: CGFloat = 0 var maxDelta: CGFloat = CGFloat.greatestFiniteMagnitude override func copy(with zone: NSZone?) -> Any { let copy = super.copy(with: zone) as! StratchyLayoutAttributes copy.deltaY = deltaY return copy } override func isEqual(_ object: Any?) -> Bool { if let attributes = object as? StratchyLayoutAttributes { if attributes.deltaY == deltaY { return super.isEqual(object) } } return false } } class StratchyHeaderLayout: UICollectionViewFlowLayout, StratchyHeaderDelegate { var headerSize = CGSize.zero var maxDelta: CGFloat = CGFloat.greatestFiniteMagnitude let minCellWidth: CGFloat = 300 let cellAspectRatio: CGFloat = 370/46 override class var layoutAttributesClass : AnyClass { return StratchyLayoutAttributes.self } override var collectionViewContentSize : CGSize { sectionInset.bottom = 15.0 sectionInset.top = 5.0 minimumInteritemSpacing = sectionInset.left let width = self.collectionView!.bounds.width - sectionInset.left - sectionInset.right let numberOfColumns = Int(width / minCellWidth) let cellWidth = CGFloat((Int(width) - Int(minimumInteritemSpacing) * (numberOfColumns - 1)) / numberOfColumns) let cellHeight = cellWidth / cellAspectRatio self.itemSize = CGSize(width: cellWidth, height: cellHeight) return super.collectionViewContentSize } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let insets = collectionView!.contentInset let offset = collectionView!.contentOffset let minY = -insets.top let attributes = super.layoutAttributesForElements(in: rect) if let stratchyAttributes = attributes as? [StratchyLayoutAttributes] { // Check if we've pulled below past the lowest position if (offset.y < minY){ let deltaY = fabs(offset.y - minY) for attribute in stratchyAttributes{ if (attribute.indexPath.section == 0){ if let kind = attribute.representedElementKind{ if (kind == UICollectionElementKindSectionHeader) { var headerRect = attribute.frame headerRect.size.height = min(headerSize.height + maxDelta, max(minY, headerSize.height + deltaY)); headerRect.origin.y = headerRect.minY - deltaY; attribute.frame = headerRect attribute.deltaY = deltaY attribute.maxDelta = maxDelta } } } } } } return attributes } // MARK: - StratchyHeaderDelegate func stratchyHeader(_ header: StratchyHeader, didResetMaxStratchValue value: CGFloat) { maxDelta = value } } ================================================ FILE: PopcornTime.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 0C0A54281AAEE7CB00B4B9B3 /* ImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0A54271AAEE7CB00B4B9B3 /* ImageProvider.swift */; }; 0C43395E1AD427BD00542A9B /* StratchyHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C43395D1AD427BD00542A9B /* StratchyHeader.xib */; }; 0C4F84C51AB2E21C00779B1A /* StratchyHeaderLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4F84C41AB2E21C00779B1A /* StratchyHeaderLayout.swift */; }; 0C4F84C71AB32D3A00779B1A /* BarHidingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4F84C61AB32D3A00779B1A /* BarHidingViewController.swift */; }; 0C4F84CE1AB385D600779B1A /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4F84CC1AB385D600779B1A /* EpisodeCell.swift */; }; 0C4F84CF1AB385D600779B1A /* EpisodeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C4F84CD1AB385D600779B1A /* EpisodeCell.xib */; }; 0C5312D11AACE65A001CB097 /* ShowCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C5312CF1AACE65A001CB097 /* ShowCollectionViewCell.swift */; }; 0C5312D21AACE65A001CB097 /* ShowCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C5312D01AACE65A001CB097 /* ShowCollectionViewCell.xib */; }; 0C77A4191AD2CA8300F8B610 /* StratchyHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C77A4181AD2CA8300F8B610 /* StratchyHeader.swift */; }; 0C7FB3881AAF3D57007AF38C /* MoreShowsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7FB3861AAF3D57007AF38C /* MoreShowsCollectionViewCell.swift */; }; 0C7FB3891AAF3D57007AF38C /* MoreShowsCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C7FB3871AAF3D57007AF38C /* MoreShowsCollectionViewCell.xib */; }; 0C90B9B71AE110D4000D4B10 /* SeasonHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C90B9B51AE110D4000D4B10 /* SeasonHeader.swift */; }; 0C90B9B81AE110D4000D4B10 /* SeasonHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C90B9B61AE110D4000D4B10 /* SeasonHeader.xib */; }; 0CC1A6761ABB55A6008F7A31 /* PagedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC1A6751ABB55A6008F7A31 /* PagedViewController.swift */; }; 4348D3021B38161800028043 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3011B38161800028043 /* AudioToolbox.framework */; }; 4348D3041B38162200028043 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3031B38162200028043 /* CFNetwork.framework */; }; 4348D3061B38162F00028043 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3051B38162F00028043 /* CoreGraphics.framework */; }; 4348D3081B38163700028043 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3071B38163700028043 /* CoreLocation.framework */; }; 4348D30A1B38164300028043 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3091B38164300028043 /* MobileCoreServices.framework */; }; 4348D30C1B38164D00028043 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D30B1B38164D00028043 /* QuartzCore.framework */; }; 4348D30E1B38165900028043 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D30D1B38165900028043 /* Security.framework */; }; 4348D3121B38166F00028043 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3111B38166F00028043 /* SystemConfiguration.framework */; }; 4348D3141B38168800028043 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3131B38168800028043 /* libsqlite3.dylib */; }; 4348D3181B38179100028043 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3171B38179100028043 /* libz.dylib */; }; 4348D31D1B3817D800028043 /* Accounts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3191B3817A400028043 /* Accounts.framework */; }; 4348D31E1B3817DF00028043 /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D31B1B3817AB00028043 /* Social.framework */; }; 65ADB189759E25BAEE4DD696 /* libPods-PopcornTime.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E136A40A06A3FB238998C59 /* libPods-PopcornTime.a */; }; 773399A41AF7E43A008DF31A /* Launch Screen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 773399A31AF7E43A008DF31A /* Launch Screen.xib */; }; 773399A61AF7E4C8008DF31A /* FavoritesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 773399A51AF7E4C8008DF31A /* FavoritesViewController.swift */; }; 773399A81AF7E4E8008DF31A /* ColorfullTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 773399A71AF7E4E8008DF31A /* ColorfullTabBarController.swift */; }; 77F3FE931C7F3F6800E18D42 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77F3FE921C7F3F6800E18D42 /* AVFoundation.framework */; }; 7F01A53E1ABD953900D2B923 /* Anime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F01A53A1ABD953900D2B923 /* Anime.swift */; }; 7F01A53F1ABD953900D2B923 /* BasicInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F01A53B1ABD953900D2B923 /* BasicInfo.swift */; }; 7F01A5401ABD953900D2B923 /* Movie.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F01A53C1ABD953900D2B923 /* Movie.swift */; }; 7F01A5411ABD953900D2B923 /* Show.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F01A53D1ABD953900D2B923 /* Show.swift */; }; 7F01A5431ABD9BB600D2B923 /* BaseStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F01A5421ABD9BB600D2B923 /* BaseStructures.swift */; }; 7F0420501ABDCE4E00E27FA1 /* AnimeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F04204B1ABDCE4E00E27FA1 /* AnimeViewController.swift */; }; 7F0420511ABDCE4E00E27FA1 /* BaseCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F04204C1ABDCE4E00E27FA1 /* BaseCollectionViewController.swift */; }; 7F0420521ABDCE4E00E27FA1 /* MoviesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F04204D1ABDCE4E00E27FA1 /* MoviesViewController.swift */; }; 7F0420531ABDCE4E00E27FA1 /* ShowDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F04204E1ABDCE4E00E27FA1 /* ShowDetailsViewController.swift */; }; 7F0420541ABDCE4E00E27FA1 /* ShowsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F04204F1ABDCE4E00E27FA1 /* ShowsViewController.swift */; }; 7F0420561ABDD3A100E27FA1 /* BaseDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0420551ABDD3A100E27FA1 /* BaseDetailsViewController.swift */; }; 7F0420591ABDD7D700E27FA1 /* AnimeDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0420571ABDD7D700E27FA1 /* AnimeDetailsViewController.swift */; }; 7F04205A1ABDD7D700E27FA1 /* MovieDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0420581ABDD7D700E27FA1 /* MovieDetailsViewController.swift */; }; 7F083C661AB36D84001C938B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F083C651AB36D84001C938B /* AppDelegate.swift */; }; 7F095FDE1AD059F000C10D98 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F095FDD1AD059F000C10D98 /* SettingsViewController.swift */; }; 7F4992A21A2491DF00D8D36E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7F4992981A2491DF00D8D36E /* Images.xcassets */; }; 7F5047C31AC2095200521FC4 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F5047C21AC2095200521FC4 /* DataManager.swift */; }; 7F540A271AC8ADE100BCC4AC /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F540A261AC8ADE100BCC4AC /* Extensions.swift */; }; 7F8B3EB31ABDC55500BE192D /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F8B3EB21ABDC55500BE192D /* Image.swift */; }; 7F8D1A671A9D430100C236BC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7F8D1A661A9D430100C236BC /* Main.storyboard */; }; 7F8D1A6A1A9D460000C236BC /* PTAPIManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F8D1A691A9D460000C236BC /* PTAPIManager.m */; }; 7FB0E6DC1A9CCC6100DA2454 /* libiconv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FB0E6DB1A9CCC6100DA2454 /* libiconv.dylib */; }; 7FB0E6DE1A9CCC7E00DA2454 /* libbz2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FB0E6DD1A9CCC7E00DA2454 /* libbz2.dylib */; }; 7FCFC3A01A23351700B3B8C2 /* libstdc++.6.0.9.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FCFC3951A23341600B3B8C2 /* libstdc++.6.0.9.dylib */; }; 7FD762A01AB632E300BD462C /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FD7629C1AB632E300BD462C /* LoadingViewController.swift */; }; 7FD762A21AB632E300BD462C /* OAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FD7629E1AB632E300BD462C /* OAuthViewController.swift */; }; 7FD9B3631A9A89E400DB89B7 /* PTTorrentStreamer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7FD9B3621A9A89E400DB89B7 /* PTTorrentStreamer.mm */; }; 7FFEB1971AD079AB00344094 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7FFEB1961AD079AB00344094 /* OpenSans-Regular.ttf */; }; 7FFEB19D1AD081C900344094 /* VDLPlaybackViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FFEB19A1AD081C900344094 /* VDLPlaybackViewController.m */; }; 7FFEB19E1AD081C900344094 /* VDLPlaybackViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7FFEB19B1AD081C900344094 /* VDLPlaybackViewController.xib */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0C0A54271AAEE7CB00B4B9B3 /* ImageProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageProvider.swift; sourceTree = ""; }; 0C0C18FED81000FDE3F1BAC8 /* Pods-PopcornTime.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PopcornTime.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PopcornTime/Pods-PopcornTime.debug.xcconfig"; sourceTree = ""; }; 0C43395D1AD427BD00542A9B /* StratchyHeader.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StratchyHeader.xib; sourceTree = ""; }; 0C4F84C41AB2E21C00779B1A /* StratchyHeaderLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StratchyHeaderLayout.swift; sourceTree = ""; }; 0C4F84C61AB32D3A00779B1A /* BarHidingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BarHidingViewController.swift; path = Controllers/BarHidingViewController.swift; sourceTree = ""; }; 0C4F84CC1AB385D600779B1A /* EpisodeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = ""; }; 0C4F84CD1AB385D600779B1A /* EpisodeCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EpisodeCell.xib; sourceTree = ""; }; 0C5312C91AACDF34001CB097 /* PopcornTime-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PopcornTime-Bridging-Header.h"; sourceTree = ""; }; 0C5312CF1AACE65A001CB097 /* ShowCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShowCollectionViewCell.swift; sourceTree = ""; }; 0C5312D01AACE65A001CB097 /* ShowCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShowCollectionViewCell.xib; sourceTree = ""; }; 0C77A4181AD2CA8300F8B610 /* StratchyHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StratchyHeader.swift; sourceTree = ""; }; 0C7FB3861AAF3D57007AF38C /* MoreShowsCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoreShowsCollectionViewCell.swift; sourceTree = ""; }; 0C7FB3871AAF3D57007AF38C /* MoreShowsCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MoreShowsCollectionViewCell.xib; sourceTree = ""; }; 0C90B9B51AE110D4000D4B10 /* SeasonHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeasonHeader.swift; sourceTree = ""; }; 0C90B9B61AE110D4000D4B10 /* SeasonHeader.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SeasonHeader.xib; sourceTree = ""; }; 0CC1A6751ABB55A6008F7A31 /* PagedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PagedViewController.swift; path = Controllers/PagedViewController.swift; sourceTree = ""; }; 4348D3011B38161800028043 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 4348D3031B38162200028043 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 4348D3051B38162F00028043 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 4348D3071B38163700028043 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; 4348D3091B38164300028043 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 4348D30B1B38164D00028043 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 4348D30D1B38165900028043 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 4348D3111B38166F00028043 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 4348D3131B38168800028043 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; }; 4348D3171B38179100028043 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; 4348D3191B3817A400028043 /* Accounts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accounts.framework; path = System/Library/Frameworks/Accounts.framework; sourceTree = SDKROOT; }; 4348D31B1B3817AB00028043 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; 5892EA13CDF70C028E9A7CB2 /* Pods-PopcornTime.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PopcornTime.release.xcconfig"; path = "Pods/Target Support Files/Pods-PopcornTime/Pods-PopcornTime.release.xcconfig"; sourceTree = ""; }; 5E136A40A06A3FB238998C59 /* libPods-PopcornTime.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PopcornTime.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 60E49EA58E27726D5CF6EA80 /* Pods-PopcornTime.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PopcornTime.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PopcornTime/Pods-PopcornTime.debug.xcconfig"; sourceTree = ""; }; 773399A31AF7E43A008DF31A /* Launch Screen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = "Launch Screen.xib"; path = "PopcornTime/Resources/Launch Screen.xib"; sourceTree = SOURCE_ROOT; }; 773399A51AF7E4C8008DF31A /* FavoritesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FavoritesViewController.swift; path = Controllers/FavoritesViewController.swift; sourceTree = ""; }; 773399A71AF7E4E8008DF31A /* ColorfullTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ColorfullTabBarController.swift; path = Controllers/ColorfullTabBarController.swift; sourceTree = ""; }; 77F3FE921C7F3F6800E18D42 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 7F01A53A1ABD953900D2B923 /* Anime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Anime.swift; sourceTree = ""; }; 7F01A53B1ABD953900D2B923 /* BasicInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicInfo.swift; sourceTree = ""; }; 7F01A53C1ABD953900D2B923 /* Movie.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Movie.swift; sourceTree = ""; }; 7F01A53D1ABD953900D2B923 /* Show.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Show.swift; sourceTree = ""; }; 7F01A5421ABD9BB600D2B923 /* BaseStructures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseStructures.swift; sourceTree = ""; }; 7F04204B1ABDCE4E00E27FA1 /* AnimeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AnimeViewController.swift; path = Controllers/AnimeViewController.swift; sourceTree = ""; }; 7F04204C1ABDCE4E00E27FA1 /* BaseCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BaseCollectionViewController.swift; path = Controllers/BaseCollectionViewController.swift; sourceTree = ""; }; 7F04204D1ABDCE4E00E27FA1 /* MoviesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MoviesViewController.swift; path = Controllers/MoviesViewController.swift; sourceTree = ""; }; 7F04204E1ABDCE4E00E27FA1 /* ShowDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ShowDetailsViewController.swift; path = Controllers/ShowDetailsViewController.swift; sourceTree = ""; }; 7F04204F1ABDCE4E00E27FA1 /* ShowsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ShowsViewController.swift; path = Controllers/ShowsViewController.swift; sourceTree = ""; }; 7F0420551ABDD3A100E27FA1 /* BaseDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BaseDetailsViewController.swift; path = Controllers/BaseDetailsViewController.swift; sourceTree = ""; }; 7F0420571ABDD7D700E27FA1 /* AnimeDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AnimeDetailsViewController.swift; path = Controllers/AnimeDetailsViewController.swift; sourceTree = ""; }; 7F0420581ABDD7D700E27FA1 /* MovieDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MovieDetailsViewController.swift; path = Controllers/MovieDetailsViewController.swift; sourceTree = ""; }; 7F083C651AB36D84001C938B /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7F095FDD1AD059F000C10D98 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SettingsViewController.swift; path = Controllers/SettingsViewController.swift; sourceTree = ""; }; 7F4992981A2491DF00D8D36E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Resources/Images.xcassets; sourceTree = ""; }; 7F5047C21AC2095200521FC4 /* DataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; }; 7F540A261AC8ADE100BCC4AC /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 7F8824C71A227A0100E2710A /* PopcornTime.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PopcornTime.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7F8B3EB21ABDC55500BE192D /* Image.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; }; 7F8D1A661A9D430100C236BC /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Resources/Main.storyboard; sourceTree = ""; }; 7F8D1A681A9D460000C236BC /* PTAPIManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PTAPIManager.h; sourceTree = ""; }; 7F8D1A691A9D460000C236BC /* PTAPIManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PTAPIManager.m; sourceTree = ""; }; 7FB0E6DB1A9CCC6100DA2454 /* libiconv.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libiconv.dylib; path = usr/lib/libiconv.dylib; sourceTree = SDKROOT; }; 7FB0E6DD1A9CCC7E00DA2454 /* libbz2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libbz2.dylib; path = usr/lib/libbz2.dylib; sourceTree = SDKROOT; }; 7FCFC3951A23341600B3B8C2 /* libstdc++.6.0.9.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libstdc++.6.0.9.dylib"; path = "usr/lib/libstdc++.6.0.9.dylib"; sourceTree = SDKROOT; }; 7FD2C38D1A233C9000CA896A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7FD7629C1AB632E300BD462C /* LoadingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoadingViewController.swift; path = Controllers/LoadingViewController.swift; sourceTree = ""; }; 7FD7629E1AB632E300BD462C /* OAuthViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OAuthViewController.swift; path = Controllers/OAuthViewController.swift; sourceTree = ""; }; 7FD9B3611A9A89E400DB89B7 /* PTTorrentStreamer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PTTorrentStreamer.h; sourceTree = ""; }; 7FD9B3621A9A89E400DB89B7 /* PTTorrentStreamer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PTTorrentStreamer.mm; sourceTree = ""; }; 7FFEB1961AD079AB00344094 /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Regular.ttf"; path = "Resources/OpenSans-Regular.ttf"; sourceTree = ""; }; 7FFEB1991AD081C900344094 /* VDLPlaybackViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VDLPlaybackViewController.h; sourceTree = ""; }; 7FFEB19A1AD081C900344094 /* VDLPlaybackViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VDLPlaybackViewController.m; sourceTree = ""; }; 7FFEB19B1AD081C900344094 /* VDLPlaybackViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = VDLPlaybackViewController.xib; sourceTree = ""; }; C2ADA07C3D291A9561B464E9 /* Pods-PopcornTime.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PopcornTime.release.xcconfig"; path = "Pods/Target Support Files/Pods-PopcornTime/Pods-PopcornTime.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 7F8824C41A227A0100E2710A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 77F3FE931C7F3F6800E18D42 /* AVFoundation.framework in Frameworks */, 4348D31E1B3817DF00028043 /* Social.framework in Frameworks */, 4348D31D1B3817D800028043 /* Accounts.framework in Frameworks */, 4348D3181B38179100028043 /* libz.dylib in Frameworks */, 4348D3141B38168800028043 /* libsqlite3.dylib in Frameworks */, 4348D3121B38166F00028043 /* SystemConfiguration.framework in Frameworks */, 4348D30E1B38165900028043 /* Security.framework in Frameworks */, 4348D30C1B38164D00028043 /* QuartzCore.framework in Frameworks */, 4348D30A1B38164300028043 /* MobileCoreServices.framework in Frameworks */, 4348D3081B38163700028043 /* CoreLocation.framework in Frameworks */, 4348D3061B38162F00028043 /* CoreGraphics.framework in Frameworks */, 4348D3041B38162200028043 /* CFNetwork.framework in Frameworks */, 4348D3021B38161800028043 /* AudioToolbox.framework in Frameworks */, 7FB0E6DE1A9CCC7E00DA2454 /* libbz2.dylib in Frameworks */, 7FB0E6DC1A9CCC6100DA2454 /* libiconv.dylib in Frameworks */, 7FCFC3A01A23351700B3B8C2 /* libstdc++.6.0.9.dylib in Frameworks */, 65ADB189759E25BAEE4DD696 /* libPods-PopcornTime.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0C5312CE1AACE5AF001CB097 /* Views */ = { isa = PBXGroup; children = ( 0C4F84CC1AB385D600779B1A /* EpisodeCell.swift */, 0C4F84CD1AB385D600779B1A /* EpisodeCell.xib */, 0C5312CF1AACE65A001CB097 /* ShowCollectionViewCell.swift */, 0C5312D01AACE65A001CB097 /* ShowCollectionViewCell.xib */, 0C7FB3861AAF3D57007AF38C /* MoreShowsCollectionViewCell.swift */, 0C7FB3871AAF3D57007AF38C /* MoreShowsCollectionViewCell.xib */, 0C90B9B51AE110D4000D4B10 /* SeasonHeader.swift */, 0C90B9B61AE110D4000D4B10 /* SeasonHeader.xib */, 0C4F84C41AB2E21C00779B1A /* StratchyHeaderLayout.swift */, 0C77A4181AD2CA8300F8B610 /* StratchyHeader.swift */, 0C43395D1AD427BD00542A9B /* StratchyHeader.xib */, ); path = Views; sourceTree = ""; }; 4EBD161BC990BBB37DA12395 /* Pods */ = { isa = PBXGroup; children = ( 0C0C18FED81000FDE3F1BAC8 /* Pods-PopcornTime.debug.xcconfig */, C2ADA07C3D291A9561B464E9 /* Pods-PopcornTime.release.xcconfig */, 60E49EA58E27726D5CF6EA80 /* Pods-PopcornTime.debug.xcconfig */, 5892EA13CDF70C028E9A7CB2 /* Pods-PopcornTime.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 7F01A5481ABD9F5800D2B923 /* Base */ = { isa = PBXGroup; children = ( 7F01A5421ABD9BB600D2B923 /* BaseStructures.swift */, 7F8B3EB21ABDC55500BE192D /* Image.swift */, 7F01A53B1ABD953900D2B923 /* BasicInfo.swift */, 7F01A53D1ABD953900D2B923 /* Show.swift */, 7F01A53C1ABD953900D2B923 /* Movie.swift */, 7F01A53A1ABD953900D2B923 /* Anime.swift */, ); name = Base; sourceTree = ""; }; 7F4992B71A24928200D8D36E /* Models */ = { isa = PBXGroup; children = ( 7F083C651AB36D84001C938B /* AppDelegate.swift */, 7F8D1A681A9D460000C236BC /* PTAPIManager.h */, 7F8D1A691A9D460000C236BC /* PTAPIManager.m */, 7F5047C21AC2095200521FC4 /* DataManager.swift */, 7FD9B3611A9A89E400DB89B7 /* PTTorrentStreamer.h */, 7FD9B3621A9A89E400DB89B7 /* PTTorrentStreamer.mm */, 0C0A54271AAEE7CB00B4B9B3 /* ImageProvider.swift */, 7F540A261AC8ADE100BCC4AC /* Extensions.swift */, 7F01A5481ABD9F5800D2B923 /* Base */, ); path = Models; sourceTree = ""; }; 7F4992DE1A24928C00D8D36E /* Thirdparties */ = { isa = PBXGroup; children = ( 7FFEB1981AD081C900344094 /* Dropin-Player */, ); name = Thirdparties; sourceTree = ""; }; 7F8824BE1A227A0000E2710A = { isa = PBXGroup; children = ( 7F8824C91A227A0100E2710A /* PopcornTime */, 7FCFC2F01A227FA800B3B8C2 /* Frameworks */, 7F8824C81A227A0100E2710A /* Products */, 4EBD161BC990BBB37DA12395 /* Pods */, ); indentWidth = 4; sourceTree = ""; tabWidth = 4; }; 7F8824C81A227A0100E2710A /* Products */ = { isa = PBXGroup; children = ( 7F8824C71A227A0100E2710A /* PopcornTime.app */, ); name = Products; sourceTree = ""; }; 7F8824C91A227A0100E2710A /* PopcornTime */ = { isa = PBXGroup; children = ( 7F4992DE1A24928C00D8D36E /* Thirdparties */, 7F4992B71A24928200D8D36E /* Models */, 0C5312CE1AACE5AF001CB097 /* Views */, 7F8D1A6C1A9DB78100C236BC /* Controllers */, 7FCFC31B1A2281B900B3B8C2 /* Resources */, 7F8824CA1A227A0100E2710A /* Supporting Files */, ); path = PopcornTime; sourceTree = ""; }; 7F8824CA1A227A0100E2710A /* Supporting Files */ = { isa = PBXGroup; children = ( 0C5312C91AACDF34001CB097 /* PopcornTime-Bridging-Header.h */, 7FD2C38D1A233C9000CA896A /* Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; 7F8D1A6C1A9DB78100C236BC /* Controllers */ = { isa = PBXGroup; children = ( 7FD7629E1AB632E300BD462C /* OAuthViewController.swift */, 7FD7629C1AB632E300BD462C /* LoadingViewController.swift */, 0C4F84C61AB32D3A00779B1A /* BarHidingViewController.swift */, 7F04204C1ABDCE4E00E27FA1 /* BaseCollectionViewController.swift */, 0CC1A6751ABB55A6008F7A31 /* PagedViewController.swift */, 773399A51AF7E4C8008DF31A /* FavoritesViewController.swift */, 7F0420551ABDD3A100E27FA1 /* BaseDetailsViewController.swift */, 7F04204F1ABDCE4E00E27FA1 /* ShowsViewController.swift */, 7F04204E1ABDCE4E00E27FA1 /* ShowDetailsViewController.swift */, 7F04204D1ABDCE4E00E27FA1 /* MoviesViewController.swift */, 7F0420581ABDD7D700E27FA1 /* MovieDetailsViewController.swift */, 7F04204B1ABDCE4E00E27FA1 /* AnimeViewController.swift */, 7F0420571ABDD7D700E27FA1 /* AnimeDetailsViewController.swift */, 7F095FDD1AD059F000C10D98 /* SettingsViewController.swift */, 773399A71AF7E4E8008DF31A /* ColorfullTabBarController.swift */, ); name = Controllers; sourceTree = ""; }; 7FCFC2F01A227FA800B3B8C2 /* Frameworks */ = { isa = PBXGroup; children = ( 77F3FE921C7F3F6800E18D42 /* AVFoundation.framework */, 4348D31B1B3817AB00028043 /* Social.framework */, 4348D3191B3817A400028043 /* Accounts.framework */, 4348D3171B38179100028043 /* libz.dylib */, 4348D3131B38168800028043 /* libsqlite3.dylib */, 4348D3111B38166F00028043 /* SystemConfiguration.framework */, 4348D30D1B38165900028043 /* Security.framework */, 4348D30B1B38164D00028043 /* QuartzCore.framework */, 4348D3091B38164300028043 /* MobileCoreServices.framework */, 4348D3071B38163700028043 /* CoreLocation.framework */, 4348D3051B38162F00028043 /* CoreGraphics.framework */, 4348D3031B38162200028043 /* CFNetwork.framework */, 4348D3011B38161800028043 /* AudioToolbox.framework */, 7FCFC3951A23341600B3B8C2 /* libstdc++.6.0.9.dylib */, 7FB0E6DD1A9CCC7E00DA2454 /* libbz2.dylib */, 7FB0E6DB1A9CCC6100DA2454 /* libiconv.dylib */, 5E136A40A06A3FB238998C59 /* libPods-PopcornTime.a */, ); name = Frameworks; sourceTree = ""; }; 7FCFC31B1A2281B900B3B8C2 /* Resources */ = { isa = PBXGroup; children = ( 773399A31AF7E43A008DF31A /* Launch Screen.xib */, 7F8D1A661A9D430100C236BC /* Main.storyboard */, 7F4992981A2491DF00D8D36E /* Images.xcassets */, 7FFEB1961AD079AB00344094 /* OpenSans-Regular.ttf */, ); name = Resources; sourceTree = ""; }; 7FFEB1981AD081C900344094 /* Dropin-Player */ = { isa = PBXGroup; children = ( 7FFEB1991AD081C900344094 /* VDLPlaybackViewController.h */, 7FFEB19A1AD081C900344094 /* VDLPlaybackViewController.m */, 7FFEB19B1AD081C900344094 /* VDLPlaybackViewController.xib */, ); name = "Dropin-Player"; path = "Thirdparties/VLCKit/Dropin-Player"; sourceTree = SOURCE_ROOT; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 7F8824C61A227A0100E2710A /* PopcornTime */ = { isa = PBXNativeTarget; buildConfigurationList = 7F8824EA1A227A0100E2710A /* Build configuration list for PBXNativeTarget "PopcornTime" */; buildPhases = ( C38234B7FF1C570F03EDA121 /* Check Pods Manifest.lock */, 7F8824C31A227A0100E2710A /* Sources */, 7F8824C41A227A0100E2710A /* Frameworks */, 7F8824C51A227A0100E2710A /* Resources */, 814B281F6347C227DBD77ABF /* Copy Pods Resources */, 7742C4881B038132001B783E /* ShellScript */, 7BC4B191B7B88187B684FB7B /* Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = PopcornTime; productName = PopcornTime; productReference = 7F8824C71A227A0100E2710A /* PopcornTime.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 7F8824BF1A227A0000E2710A /* Project object */ = { isa = PBXProject; attributes = { CLASSPREFIX = ""; LastSwiftMigration = 0700; LastSwiftUpdateCheck = 0700; LastUpgradeCheck = 0820; ORGANIZATIONNAME = PopcornTime; TargetAttributes = { 7F8824C61A227A0100E2710A = { CreatedOnToolsVersion = 6.1; DevelopmentTeam = DBR2MMCRF5; LastSwiftMigration = 0820; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.InAppPurchase = { enabled = 0; }; }; }; }; }; buildConfigurationList = 7F8824C21A227A0100E2710A /* Build configuration list for PBXProject "PopcornTime" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 7F8824BE1A227A0000E2710A; productRefGroup = 7F8824C81A227A0100E2710A /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 7F8824C61A227A0100E2710A /* PopcornTime */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 7F8824C51A227A0100E2710A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 0C7FB3891AAF3D57007AF38C /* MoreShowsCollectionViewCell.xib in Resources */, 0C5312D21AACE65A001CB097 /* ShowCollectionViewCell.xib in Resources */, 0C43395E1AD427BD00542A9B /* StratchyHeader.xib in Resources */, 7FFEB19E1AD081C900344094 /* VDLPlaybackViewController.xib in Resources */, 7F8D1A671A9D430100C236BC /* Main.storyboard in Resources */, 7F4992A21A2491DF00D8D36E /* Images.xcassets in Resources */, 0C90B9B81AE110D4000D4B10 /* SeasonHeader.xib in Resources */, 7FFEB1971AD079AB00344094 /* OpenSans-Regular.ttf in Resources */, 0C4F84CF1AB385D600779B1A /* EpisodeCell.xib in Resources */, 773399A41AF7E43A008DF31A /* Launch Screen.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 7742C4881B038132001B783E /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "if [ -n \"$FABRIC_API_KEY\" ] && [ -n \"$FABRIC_BUILD_SECRET\" ]; then\n ${PODS_ROOT}/Fabric/run ${FABRIC_API_KEY} ${FABRIC_BUILD_SECRET} || true\nelse\n echo \"FABRIC_API_KEY and/or FABRIC_BUILD_SECRET are not set\"\nfi"; }; 7BC4B191B7B88187B684FB7B /* Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PopcornTime/Pods-PopcornTime-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 814B281F6347C227DBD77ABF /* Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PopcornTime/Pods-PopcornTime-resources.sh\"\n"; showEnvVarsInLog = 0; }; C38234B7FF1C570F03EDA121 /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 7F8824C31A227A0100E2710A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 7F5047C31AC2095200521FC4 /* DataManager.swift in Sources */, 7F8B3EB31ABDC55500BE192D /* Image.swift in Sources */, 7F0420561ABDD3A100E27FA1 /* BaseDetailsViewController.swift in Sources */, 7FD9B3631A9A89E400DB89B7 /* PTTorrentStreamer.mm in Sources */, 7FD762A01AB632E300BD462C /* LoadingViewController.swift in Sources */, 7F01A5401ABD953900D2B923 /* Movie.swift in Sources */, 0C5312D11AACE65A001CB097 /* ShowCollectionViewCell.swift in Sources */, 7F0420541ABDCE4E00E27FA1 /* ShowsViewController.swift in Sources */, 773399A61AF7E4C8008DF31A /* FavoritesViewController.swift in Sources */, 7F01A5411ABD953900D2B923 /* Show.swift in Sources */, 7F540A271AC8ADE100BCC4AC /* Extensions.swift in Sources */, 0C77A4191AD2CA8300F8B610 /* StratchyHeader.swift in Sources */, 7F01A5431ABD9BB600D2B923 /* BaseStructures.swift in Sources */, 0C4F84CE1AB385D600779B1A /* EpisodeCell.swift in Sources */, 773399A81AF7E4E8008DF31A /* ColorfullTabBarController.swift in Sources */, 7F01A53F1ABD953900D2B923 /* BasicInfo.swift in Sources */, 7FFEB19D1AD081C900344094 /* VDLPlaybackViewController.m in Sources */, 0C4F84C71AB32D3A00779B1A /* BarHidingViewController.swift in Sources */, 7F8D1A6A1A9D460000C236BC /* PTAPIManager.m in Sources */, 0C7FB3881AAF3D57007AF38C /* MoreShowsCollectionViewCell.swift in Sources */, 0CC1A6761ABB55A6008F7A31 /* PagedViewController.swift in Sources */, 7FD762A21AB632E300BD462C /* OAuthViewController.swift in Sources */, 7F0420591ABDD7D700E27FA1 /* AnimeDetailsViewController.swift in Sources */, 7F095FDE1AD059F000C10D98 /* SettingsViewController.swift in Sources */, 7F0420531ABDCE4E00E27FA1 /* ShowDetailsViewController.swift in Sources */, 7F0420501ABDCE4E00E27FA1 /* AnimeViewController.swift in Sources */, 0C4F84C51AB2E21C00779B1A /* StratchyHeaderLayout.swift in Sources */, 7F01A53E1ABD953900D2B923 /* Anime.swift in Sources */, 7F0420511ABDCE4E00E27FA1 /* BaseCollectionViewController.swift in Sources */, 7F04205A1ABDD7D700E27FA1 /* MovieDetailsViewController.swift in Sources */, 7F0420521ABDCE4E00E27FA1 /* MoviesViewController.swift in Sources */, 0C0A54281AAEE7CB00B4B9B3 /* ImageProvider.swift in Sources */, 7F083C661AB36D84001C938B /* AppDelegate.swift in Sources */, 0C90B9B71AE110D4000D4B10 /* SeasonHeader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 7F8824E81A227A0100E2710A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEVELOPMENT_TEAM = DBR2MMCRF5; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "-D DEBUG"; SDKROOT = iphoneos; }; name = Debug; }; 7F8824E91A227A0100E2710A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = YES; DEVELOPMENT_TEAM = DBR2MMCRF5; ENABLE_BITCODE = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; VALIDATE_PRODUCT = YES; }; name = Release; }; 7F8824EB1A227A0100E2710A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 60E49EA58E27726D5CF6EA80 /* Pods-PopcornTime.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/PopcornTime/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "-DDEBUG"; PRODUCT_BUNDLE_IDENTIFIER = com.popcorntime.ios.dk; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "PopcornTime/PopcornTime-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "arm64 armv7 armv7s"; }; name = Debug; }; 7F8824EC1A227A0100E2710A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5892EA13CDF70C028E9A7CB2 /* Pods-PopcornTime.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/PopcornTime/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_SWIFT_FLAGS = "-DRELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.popcorntime.ios.dk; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "PopcornTime/PopcornTime-Bridging-Header.h"; SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "arm64 armv7 armv7s"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 7F8824C21A227A0100E2710A /* Build configuration list for PBXProject "PopcornTime" */ = { isa = XCConfigurationList; buildConfigurations = ( 7F8824E81A227A0100E2710A /* Debug */, 7F8824E91A227A0100E2710A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 7F8824EA1A227A0100E2710A /* Build configuration list for PBXNativeTarget "PopcornTime" */ = { isa = XCConfigurationList; buildConfigurations = ( 7F8824EB1A227A0100E2710A /* Debug */, 7F8824EC1A227A0100E2710A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 7F8824BF1A227A0000E2710A /* Project object */; } ================================================ FILE: PopcornTime.xcodeproj/xcshareddata/xcschemes/PopcornTime.xcscheme ================================================ ================================================ FILE: README.md ================================================ ## PopcornTime for iOS [![Build Status](https://www.bitrise.io/app/9ee06c0598c7cbdb.svg?token=nH-7MkkoZ7EpSlvMce4KkA)](https://www.bitrise.io/app/9ee06c0598c7cbdb) Version of PopcornTime app for iOS based on [libtorrent](http://www.libtorrent.org) and [MobileVLCKit](https://wiki.videolan.org/VLCKit/). There is still a lot of work to do, but in most cases it works. ### Screenshots ![](https://raw.github.com/danylokostyshyn/popcorntime-ios/master/Screenshots/1.png) ![](https://raw.github.com/danylokostyshyn/popcorntime-ios/master/Screenshots/2.png) ![](https://raw.github.com/danylokostyshyn/popcorntime-ios/master/Screenshots/3.png) ### Getting Started This project uses [CocoaPods](http://cocoapods.org/). ``` bash $ git clone https://github.com/danylokostyshyn/popcorntime-ios.git $ cd popcorntime-ios/ $ pod install $ open PopcornTime.xcworkspace/ ``` ================================================ FILE: Thirdparties/VLCKit/Dropin-Player/VDLPlaybackViewController.h ================================================ /* Copyright (c) 2013, Felix Paul Kühne and VideoLAN * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #import #import @class VDLPlaybackViewController; @protocol VDLPlaybackViewControllerDelegate - (void)playbackControllerDidFinishPlayback:(VDLPlaybackViewController *)playbackController; @end @interface VDLPlaybackViewController : UIViewController @property (nonatomic, strong) IBOutlet UIView *movieView; @property (nonatomic, strong) IBOutlet UISlider *positionSlider; @property (nonatomic, strong) IBOutlet UIButton *timeDisplay; @property (nonatomic, strong) IBOutlet UIButton *playPauseButton; @property (nonatomic, strong) IBOutlet UIButton *subtitleSwitcherButton; @property (nonatomic, strong) IBOutlet UIButton *audioSwitcherButton; @property (nonatomic, strong) IBOutlet UINavigationBar *toolbar; @property (nonatomic, strong) IBOutlet UIView *controllerPanel; @property (nonatomic, strong) IBOutlet MPVolumeView *volumeView; @property (nonatomic, weak) id delegate; - (void)playMediaFromURL:(NSURL*)theURL; - (IBAction)closePlayback:(id)sender; - (IBAction)positionSliderDrag:(id)sender; - (IBAction)positionSliderAction:(id)sender; - (IBAction)toggleTimeDisplay:(id)sender; - (IBAction)playandPause:(id)sender; - (IBAction)switchAudioTrack:(id)sender; - (IBAction)switchSubtitleTrack:(id)sender; - (IBAction)switchVideoDimensions:(id)sender; @end ================================================ FILE: Thirdparties/VLCKit/Dropin-Player/VDLPlaybackViewController.m ================================================ /* Copyright (c) 2013, Felix Paul Kühne and VideoLAN * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #import "VDLPlaybackViewController.h" #import #import @interface VDLPlaybackViewController () { VLCMediaPlayer *_mediaplayer; BOOL _setPosition; BOOL _displayRemainingTime; int _currentAspectRatioMask; NSArray *_aspectRatios; UIActionSheet *_audiotrackActionSheet; UIActionSheet *_subtitleActionSheet; NSURL *_url; NSTimer *_idleTimer; } @end @implementation VDLPlaybackViewController - (void)viewDidLoad { [super viewDidLoad]; /* fix-up UI */ self.wantsFullScreenLayout = YES; [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent]; /* we want to influence the system volume */ [[AVAudioSession sharedInstance] setDelegate:self]; /* populate array of supported aspect ratios (there are more!) */ _aspectRatios = @[@"DEFAULT", @"FILL_TO_SCREEN", @"4:3", @"16:9", @"16:10", @"2.21:1"]; /* fix-up the UI */ CGRect rect = self.toolbar.frame; rect.size.height += 20.; self.toolbar.frame = rect; [self.timeDisplay setTitle:@"" forState:UIControlStateNormal]; /* this looks a bit weird, but let's try to support iOS 5 */ UISlider *volumeSlider = nil; for (id aView in self.volumeView.subviews){ if ([[[aView class] description] isEqualToString:@"MPVolumeSlider"]){ volumeSlider = (UISlider *)aView; break; } } [volumeSlider addTarget:self action:@selector(volumeSliderAction:) forControlEvents:UIControlEventValueChanged]; /* setup gesture recognizer to toggle controls' visibility */ UITapGestureRecognizer *tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)]; [self.movieView addGestureRecognizer:tapOnVideoRecognizer]; } - (void)playMediaFromURL:(NSURL*)theURL { _url = theURL; } - (IBAction)playandPause:(id)sender { if (_mediaplayer.isPlaying) [_mediaplayer pause]; [_mediaplayer play]; } - (IBAction)closePlayback:(id)sender { [self.delegate playbackControllerDidFinishPlayback:self]; self.delegate = nil; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.navigationController setNavigationBarHidden:YES animated:YES]; /* setup the media player instance, give it a delegate and something to draw into */ // NSString *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject; // NSString *logFilePath = [documents stringByAppendingPathComponent:@"log.txt"]; // NSString *logParam = [NSString stringWithFormat:@"--logfile=\"%@\"", logFilePath]; _mediaplayer = [[VLCMediaPlayer alloc] initWithOptions:@[@"--avi-index=2", @"--play-and-pause"]]; _mediaplayer.delegate = self; _mediaplayer.drawable = self.movieView; /* listen for notifications from the player */ [_mediaplayer addObserver:self forKeyPath:@"time" options:0 context:nil]; [_mediaplayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil]; /* create a media object and give it to the player */ _mediaplayer.media = [VLCMedia mediaWithURL:_url]; [_mediaplayer play]; if (self.controllerPanel.hidden) [self toggleControlsVisible]; [self _resetIdleTimer]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if (_mediaplayer) { @try { [_mediaplayer removeObserver:self forKeyPath:@"time"]; [_mediaplayer removeObserver:self forKeyPath:@"remainingTime"]; } @catch (NSException *exception) { NSLog(@"we weren't an observer yet"); } if (_mediaplayer.media) [_mediaplayer stop]; if (_mediaplayer) _mediaplayer = nil; } if (_idleTimer) { [_idleTimer invalidate]; _idleTimer = nil; } [self.navigationController setNavigationBarHidden:NO animated:YES]; [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade]; } - (IBAction)positionSliderAction:(UISlider *)sender { [self _resetIdleTimer]; /* we need to limit the number of events sent by the slider, since otherwise, the user * wouldn't see the I-frames when seeking on current mobile devices. This isn't a problem * within the Simulator, but especially on older ARMv7 devices, it's clearly noticeable. */ [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3]; _setPosition = NO; } - (void)_setPositionForReal { if (!_setPosition) { _mediaplayer.position = _positionSlider.value; _setPosition = YES; } } - (IBAction)positionSliderDrag:(id)sender { [self _resetIdleTimer]; } - (IBAction)volumeSliderAction:(id)sender { [self _resetIdleTimer]; } - (void)mediaPlayerStateChanged:(NSNotification *)aNotification { VLCMediaPlayerState currentState = _mediaplayer.state; /* distruct view controller on error */ if (currentState == VLCMediaPlayerStateError) [self performSelector:@selector(closePlayback:) withObject:nil afterDelay:2.]; /* or if playback ended */ if (currentState == VLCMediaPlayerStateEnded || currentState == VLCMediaPlayerStateStopped) [self performSelector:@selector(closePlayback:) withObject:nil afterDelay:2.]; [self.playPauseButton setTitle:[_mediaplayer isPlaying]? @"Pause" : @"Play" forState:UIControlStateNormal]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { self.positionSlider.value = [_mediaplayer position]; if (_displayRemainingTime) [self.timeDisplay setTitle:[[_mediaplayer remainingTime] stringValue] forState:UIControlStateNormal]; else [self.timeDisplay setTitle:[[_mediaplayer time] stringValue] forState:UIControlStateNormal]; } - (IBAction)toggleTimeDisplay:(id)sender { [self _resetIdleTimer]; _displayRemainingTime = !_displayRemainingTime; } - (void)toggleControlsVisible { BOOL controlsHidden = !self.controllerPanel.hidden; self.controllerPanel.hidden = controlsHidden; self.toolbar.hidden = controlsHidden; [[UIApplication sharedApplication] setStatusBarHidden:controlsHidden withAnimation:UIStatusBarAnimationFade]; } - (void)_resetIdleTimer { if (!_idleTimer) _idleTimer = [NSTimer scheduledTimerWithTimeInterval:5. target:self selector:@selector(idleTimerExceeded) userInfo:nil repeats:NO]; else { if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 5.) [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:5.]]; } } - (void)idleTimerExceeded { _idleTimer = nil; if (!self.controllerPanel.hidden) [self toggleControlsVisible]; } - (IBAction)switchVideoDimensions:(id)sender { [self _resetIdleTimer]; NSUInteger count = [_aspectRatios count]; if (_currentAspectRatioMask + 1 > count - 1) { _mediaplayer.videoAspectRatio = NULL; _mediaplayer.videoCropGeometry = NULL; _currentAspectRatioMask = 0; NSLog(@"crop disabled"); } else { _currentAspectRatioMask++; if ([_aspectRatios[_currentAspectRatioMask] isEqualToString:@"FILL_TO_SCREEN"]) { UIScreen *screen = [UIScreen mainScreen]; float f_ar = screen.bounds.size.width / screen.bounds.size.height; if (f_ar == (float)(640./1136.)) // iPhone 5 aka 16:9.01 _mediaplayer.videoCropGeometry = "16:9"; else if (f_ar == (float)(2./3.)) // all other iPhones _mediaplayer.videoCropGeometry = "16:10"; // libvlc doesn't support 2:3 crop else if (f_ar == .75) // all iPads _mediaplayer.videoCropGeometry = "4:3"; else if (f_ar == .5625) // AirPlay _mediaplayer.videoCropGeometry = "16:9"; else NSLog(@"unknown screen format %f, can't crop", f_ar); NSLog(@"FILL_TO_SCREEN"); return; } _mediaplayer.videoCropGeometry = NULL; _mediaplayer.videoAspectRatio = (char *)[_aspectRatios[_currentAspectRatioMask] UTF8String]; NSLog(@"crop switched to %@", _aspectRatios[_currentAspectRatioMask]); } } - (IBAction)switchAudioTrack:(id)sender { _audiotrackActionSheet = [[UIActionSheet alloc] initWithTitle:@"audio track selector" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil]; NSArray *audioTracks = [_mediaplayer audioTrackNames]; NSArray *audioTrackIndexes = [_mediaplayer audioTrackIndexes]; NSUInteger count = [audioTracks count]; for (NSUInteger i = 0; i < count; i++) { NSString *indexIndicator = ([audioTrackIndexes[i] intValue] == [_mediaplayer currentAudioTrackIndex])? @"\u2713": @""; NSString *buttonTitle = [NSString stringWithFormat:@"%@ %@", indexIndicator, audioTracks[i]]; [_audiotrackActionSheet addButtonWithTitle:buttonTitle]; } [_audiotrackActionSheet addButtonWithTitle:@"Cancel"]; [_audiotrackActionSheet setCancelButtonIndex:[_audiotrackActionSheet numberOfButtons] - 1]; [_audiotrackActionSheet showInView:self.audioSwitcherButton]; } - (IBAction)switchSubtitleTrack:(id)sender { NSArray *spuTracks = [_mediaplayer videoSubTitlesNames]; NSArray *spuTrackIndexes = [_mediaplayer videoSubTitlesIndexes]; NSUInteger count = [spuTracks count]; if (count <= 1) return; _subtitleActionSheet = [[UIActionSheet alloc] initWithTitle:@"subtitle track selector" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil]; for (NSUInteger i = 0; i < count; i++) { NSString *indexIndicator = ([spuTrackIndexes[i] intValue] == [_mediaplayer currentVideoSubTitleIndex])? @"\u2713": @""; NSString *buttonTitle = [NSString stringWithFormat:@"%@ %@", indexIndicator, spuTracks[i]]; [_subtitleActionSheet addButtonWithTitle:buttonTitle]; } [_subtitleActionSheet addButtonWithTitle:@"Cancel"]; [_subtitleActionSheet setCancelButtonIndex:[_subtitleActionSheet numberOfButtons] - 1]; [_subtitleActionSheet showInView: self.subtitleSwitcherButton]; } - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == [actionSheet cancelButtonIndex]) return; NSArray *indexArray; if (actionSheet == _subtitleActionSheet) { indexArray = _mediaplayer.videoSubTitlesIndexes; if (buttonIndex <= indexArray.count) { _mediaplayer.currentVideoSubTitleIndex = [indexArray[buttonIndex] intValue]; } } else if (actionSheet == _audiotrackActionSheet) { indexArray = _mediaplayer.audioTrackIndexes; if (buttonIndex <= indexArray.count) { _mediaplayer.currentAudioTrackIndex = [indexArray[buttonIndex] intValue]; } } } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end ================================================ FILE: Thirdparties/VLCKit/Dropin-Player/VDLPlaybackViewController.xib ================================================ 1552 12F45 4514 1187.40 626.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin 3747 IBProxyObject IBUIBarButtonItem IBUIButton IBUINavigationBar IBUINavigationItem IBUISlider IBUIView com.apple.InterfaceBuilder.IBCocoaTouchPlugin PluginDependencyRecalculationVersion IBFilesOwner IBCocoaTouchFramework IBFirstResponder IBCocoaTouchFramework 274 274 {320, 550} _NS:9 3 MAA IBCocoaTouchFramework 290 {320, 44} _NS:9 IBCocoaTouchFramework 2 Title IBCocoaTouchFramework 269 292 {{139, 13}, {44, 26}} _NS:9 3 MCAwAA NO IBCocoaTouchFramework 0 0 4 4 0.0 0.0 3 MQA Pause 3 MC41AA 2 15 HelveticaNeue-Bold 15 16 292 {{20, 13}, {41, 26}} _NS:9 NO IBCocoaTouchFramework 0 0 4 4 0.0 0.0 Audio 292 {{238, 13}, {62, 26}} _NS:9 NO IBCocoaTouchFramework 0 0 4 4 0.0 0.0 Subtitles 292 {{20, 47}, {284, 22}} _NS:9 IBCocoaTouchFramework {{0, 408}, {320, 82}} _NS:10 3 MC42NjY2NjY2NjY3AA IBCocoaTouchFramework {{0, 20}, {320, 548}} NO IBUIScreenMetrics YES {320, 568} {568, 320} IBCocoaTouchFramework Retina 4-inch Full Screen 2 IBCocoaTouchFramework 290 290 {{5, 10}, {188, 23}} _NS:9 {250, 250} NO IBCocoaTouchFramework 0 0 0.5 289 {{241, 6}, {59, 29}} _NS:9 NO IBCocoaTouchFramework 0 0 VidDi 289 {{193, 11}, {50, 20}} _NS:9 NO IBCocoaTouchFramework 0 0 --:-- 2 13 HelveticaNeue-Bold 13 16 {300, 40} _NS:9 IBCocoaTouchFramework IBCocoaTouchFramework 1 0 view 7 movieView 29 positionSlider 179 timeDisplay 181 volumeView 182 controllerPanel 183 toolbar 184 audioSwitcherButton 190 subtitleSwitcherButton 191 playPauseButton 192 toggleTimeDisplay: 7 180 switchVideoDimensions: 7 177 positionSliderAction: 13 178 positionSliderDrag: 3 193 positionSliderDrag: 4 194 switchSubtitleTrack: 7 176 playandPause: 7 174 switchAudioTrack: 7 175 titleView 186 leftBarButtonItem 188 closePlayback: 189 0 -1 File's Owner -2 6 14 30 Time view 31 32 33 Position Slider 45 59 47 Controls panel 56 52 50 48 187 VDLPlaybackViewController com.apple.InterfaceBuilder.IBCocoaTouchPlugin UIResponder com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin MPVolumeView com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin 194 MPVolumeView UIView IBProjectSource ./Classes/MPVolumeView.h VDLPlaybackViewController UIViewController id id UISlider id id id id id id closePlayback: id playandPause: id positionSliderAction: UISlider positionSliderDrag: id switchAudioTrack: id switchSubtitleTrack: id switchVideoDimensions: id toggleTimeDisplay: id volumeSliderAction: id UIButton UIView UIView UIButton UISlider UIButton UIButton UINavigationBar MPVolumeView audioSwitcherButton UIButton controllerPanel UIView movieView UIView playPauseButton UIButton positionSlider UISlider subtitleSwitcherButton UIButton timeDisplay UIButton toolbar UINavigationBar volumeView MPVolumeView IBProjectSource ./Classes/VDLPlaybackViewController.h 0 IBCocoaTouchFramework YES com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 YES 3 3747 ================================================ FILE: popcorntime_api.paw ================================================ 134481920 8F8C1300-C155-4119-86D2-01F70529817A 142 NSPersistenceFrameworkVersion 526 NSStoreModelVersionHashes LMCookieJar Fttmf2L4PrGvKUF496+nqgVVGek45TjOe7sUMtjNg8I= LMEnvironment uzBoVFcO4YvR9/3ej4AJ1UOOsA/u5DKY2aemusoIseU= LMEnvironmentDomain yM1GPGHdquS8IWLtuczlNoqKhIhD9FW6IReSfFffJgs= LMEnvironmentVariable P8e0lYd5JZKRabS/eXVSOJ4oitilz67xtv+pLqW1Jqg= LMEnvironmentVariableValue my5hNPJ51oDCSa8EgdNxWAnRcDLcERUGjtuXnzhSxQ0= LMKeyValue bIXXbyYF2xAv2MXg8JTVFsslmMKuvsfnR86QdUcFkdM= LMRequest kYB6By9dZHqmH3YNw3h9tYPoxeG5ZWHPfhLXXp7OLFs= LMRequestGroup N3ml+gYVWc4m0LSGLnBDJ37p9isOc41y+TtaM0Eacrc= LMRequestTreeItem ak+hYb/lDeG55U0kgGvU5ej7HUltUj0RTrX5z/izNrs= NSStoreModelVersionHashesVersion 3 NSStoreModelVersionIdentifiers LMDocumentVersion3 8A4913AA-D95C-4BE4-BBA8-50A8965064BA 1 Default Jar 688A4A66-39E9-4757-9B88-BE8FF22FAF56 0 Default Environment 6CD41C5D-7A66-4EE0-BEED-013C74D9E2FD http://ytspt.re/api/list.json?limit=30\u2600order=desc\u2600sort=seeds 1 1 0 0 GET 0 0 top 0 1 8C2DA06B-D03E-4AA2-8D3B-4966CB54878F 0 3C5F00CA-5004-4FA5-8D2A-C940F479D3D5 0 Default Domain 37442FBC-2F84-4413-9141-F8A3FD57C17A http://ytspt.re/api/listimdb.json?imdb_id=tt2245084 1 1 0 0 GET 0 2 desc 0 1 0 1 20FEA1CC-A132-4A59-A394-7250AD2DC5AF http://ytspt.re/api/list.json?limit=30\u2600keywords=terminator\u2600order=desc\u2600sort=seeds\u2600set=1 1 1 0 0 GET 0 1 search 41E1A38E-9097-42C3-997C-68D3E7D245E0 http://eztvapi.re/shows/1?limit=30\u2600order=desc\u2600sort=seeds 1 1 0 0 GET 0 0 top 0 1 87BCD354-93F9-457F-8435-5C16D622B78B http://eztvapi.re/show/tt0898266 1 1 0 0 GET 0 2 desc tbbt 0 1 C0ABC6A0-CC89-4280-9191-7D0E134441BE http://eztvapi.re/shows/1?limit=30\u2600order=desc\u2600sort=seeds\u2600keywords=silicon%20valley 1 1 0 0 GET 0 1 search 0 1 0 1 C6E38767-167B-4552-A709-B0407F71C8ED http://ptp.haruhichan.com/list.php?page=1\u2600limit=50\u2600sort=popularity\u2600type=All 1 1 0 0 GET 0 0 top 0 1 367921AE-A56C-48E1-80D1-B9AA4C888534 http://ptp.haruhichan.com/anime.php?id=912 1 1 0 0 GET 0 2 desc 0 1 FEFB12B4-1BA8-4137-A79D-5AAF4D0C1CEC http://ptp.haruhichan.com/list.php?search=pokemon\u2600limit=50\u2600sort=popularity\u2600type=All 1 1 0 0 GET 0 1 search 442876A0-7D7A-4986-98DB-8F93F085A06B 1 movies 9BDE5244-0786-473C-8781-5BBC99E68567 5 shows DADAE43E-EE55-431B-A266-6AFB5ECEEEEF http://eztvapi.re/show/tt2575988 1 1 0 0 GET 0 3 desc sv 22C26C40-8D1A-4787-942F-D134607AF258 9 anime 0 1 BD8350BF-4631-44DF-9BDC-77A5458F2E4F http://cloudflare.com/api/v2/list_movies.json?page=1\u2600limit=20\u2600order_by=desc\u2600sort_by=seeds 1 1 0 0 GET 0 0 top eqwww.image.yt Host 0 1 C5597A41-F783-4F61-B3FC-A37F6FC924FB 2 deprecated eqwww.image.yt Host 0 1 43436530-B6BD-466C-A1A8-12F57EBAD1BE http://cloudflare.com/api/v2/list_movies.json?query_term=terminator 1 1 0 0 GET 0 2 search 42E5E5DA-21B7-4077-93AE-B5EF84E238F8 http://cloudflare.com/api/v2/movie_details.json?movie_id=4132 1 1 0 0 GET 0 3 desc eqwww.image.yt Host 0 1 D9A5D01E-B8E3-4F85-8F6D-4CC3B79FE78D 0 v2 1 1 1 1 1 1