Showing preview only (375K chars total). Download the full file or copy to clipboard to get everything.
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Popcorn Time</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>Fabric</key>
<dict>
<key>APIKey</key>
<string>API_KEY</string>
<key>Kits</key>
<array>
<dict>
<key>KitInfo</key>
<dict/>
<key>KitName</key>
<string>Crashlytics</string>
</dict>
</array>
</dict>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UILaunchStoryboardName</key>
<string>Launch Screen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array/>
<key>UIStatusBarHidden</key>
<true/>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleBlackOpaque</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
================================================
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 <Foundation/Foundation.h>
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 <UIKit/UIKit.h>
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 <Foundation/Foundation.h>
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 <UIKit/UIKit.h>
#import <string>
#import <libtorrent/session.hpp>
#import <libtorrent/alert.hpp>
#import <libtorrent/alert_types.hpp>
#import <CocoaSecurity/CocoaSecurity.h>
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<int> 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<torrent_handle> ths = _session->get_torrents();
for(std::vector<torrent_handle>::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<alert *> 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<alert *>::iterator it=deque.begin(); it != deque.end(); ++it) {
std::unique_ptr<alert> 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<const torrent_info> ti = th.torrent_file();
for (int i=next_required_piece; i<next_required_piece+MIN_PIECES; i++) {
if (i < ti->num_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<const torrent_info> 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<const torrent_info> 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; i<files_count; i++) {
file_entry fe = ti->file_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<const torrent_info> ti = th.torrent_file();
for(std::vector<int>::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<int> 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<const torrent_info> ti = th.torrent_file();
int first_piece = ti->map_file(file_index, 0, 0).piece;
for (int i=first_piece; i<first_piece+MIN_PIECES; i++) {
required_pieces.push_back(i);
}
size_type file_size = ti->file_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<int>::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<int>::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 <CocoaSecurity/CocoaSecurity.h>
#import <SDWebImage/UIImageView+WebCache.h>
#import <SDWebImage/SDWebImageDownloader.h>
================================================
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
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7702" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7701"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2Lc-RQ-qUE" userLabel="container">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="BigLogo" translatesAutoresizingMaskIntoConstraints="NO" id="bs5-NJ-V6F">
<rect key="frame" x="96" y="92" width="288" height="296"/>
<constraints>
<constraint firstAttribute="width" secondItem="bs5-NJ-V6F" secondAttribute="height" multiplier="245:251" id="Gm1-O8-hkp"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="bs5-NJ-V6F" firstAttribute="width" secondItem="2Lc-RQ-qUE" secondAttribute="width" multiplier="0.6" id="36t-4H-fXB"/>
<constraint firstAttribute="centerY" secondItem="bs5-NJ-V6F" secondAttribute="centerY" id="74X-ZS-r3g"/>
<constraint firstAttribute="centerX" secondItem="bs5-NJ-V6F" secondAttribute="centerX" id="wqp-gX-LAI"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="0.066666666669999999" green="0.070588235289999995" blue="0.078431372550000003" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="2Lc-RQ-qUE" secondAttribute="trailing" id="2Bj-4U-BT4"/>
<constraint firstItem="2Lc-RQ-qUE" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="JNM-Yd-jIm"/>
<constraint firstAttribute="bottom" secondItem="2Lc-RQ-qUE" secondAttribute="bottom" id="JOE-UV-868"/>
<constraint firstItem="2Lc-RQ-qUE" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="xnf-ZZ-Dah"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="404" y="445"/>
</view>
</objects>
<resources>
<image name="BigLogo" width="490" height="502"/>
</resources>
</document>
================================================
FILE: PopcornTime/Resources/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="dfX-fY-8T0">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
</dependencies>
<scenes>
<!--Colorfull Tab Bar Controller-->
<scene sceneID="go6-cJ-rM0">
<objects>
<tabBarController id="dfX-fY-8T0" customClass="ColorfullTabBarController" customModule="PopcornTime" customModuleProvider="target" sceneMemberID="viewController">
<nil key="simulatedBottomBarMetrics"/>
<tabBar key="tabBar" contentMode="scaleToFill" barStyle="black" id="FiG-nQ-Gwx">
<rect key="frame" x="0.0" y="0.0" width="320" height="49"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</tabBar>
<connections>
<segue destination="ZTe-Ju-nKb" kind="relationship" relationship="viewControllers" id="sbB-u7-VOB"/>
<segue destination="laf-Ml-R54" kind="relationship" relationship="viewControllers" id="tEU-Ig-sn6"/>
<segue destination="Q7f-F1-kvd" kind="relationship" relationship="viewControllers" id="n73-1d-JO5"/>
<segue destination="3CV-c3-dhf" kind="relationship" relationship="viewControllers" id="2PJ-Zl-Jde"/>
</connections>
</tabBarController>
<placeholder placeholderIdentifier="IBFirstResponder" id="mBP-kZ-5Xx" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="112" y="245"/>
</scene>
<!--Anime View Controller-->
<scene sceneID="L5L-Yx-H6c">
<objects>
<viewController id="ADj-go-OwF" customClass="AnimeViewController" customModule="PopcornTime" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="AUE-h9-rJL"/>
<viewControllerLayoutGuide type="bottom" id="i2R-TO-heN"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="yYN-t5-Uth">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="N8O-87-354">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="RCM-ag-Wek">
<size key="itemSize" width="50" height="50"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells/>
</collectionView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="N8O-87-354" firstAttribute="top" secondItem="yYN-t5-Uth" secondAttribute="topMargin" id="Cxr-Mw-YDd"/>
<constraint firstAttribute="bottom" secondItem="N8O-87-354" secondAttribute="bottom" id="NZx-Vo-Vo3"/>
<constraint firstItem="i2R-TO-heN" firstAttribute="top" secondItem="N8O-87-354" secondAttribute="bottom" id="OUd-9y-xga"/>
<constraint firstAttribute="trailing" secondItem="N8O-87-354" secondAttribute="trailing" id="OgF-Q6-JQm"/>
<constraint firstItem="N8O-87-354" firstAttribute="leading" secondItem="yYN-t5-Uth" secondAttribute="leading" id="TdS-vL-frL"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="OUd-9y-xga"/>
</mask>
</variation>
</view>
<navigationItem key="navigationItem" id="jQM-dh-vnA"/>
<connections>
<outlet property="collectionView" destination="N8O-87-354" id="r49-VW-vZu"/>
<outlet property="collectionViewLayout" destination="RCM-ag-Wek" id="x5y-ZA-Aax"/>
<segue destination="nkV-Ie-GtD" kind="show" identifier="showDetails" id="xDB-pc-cYk"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="FI3-gN-OJO" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1854" y="245"/>
</scene>
<!--Shows View Controller-->
<scene sceneID="BPf-qC-CkQ">
<objects>
<viewController id="n2I-Pn-G6j" customClass="ShowsViewController" customModule="PopcornTime" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="WM1-Br-eA8"/>
<viewControllerLayoutGuide type="bottom" id="8Y2-ZI-Ufs"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="IDd-dj-rlb">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="J0Q-3v-mYi">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="xZ1-3i-1vR">
<size key="itemSize" width="50" height="50"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells/>
</collectionView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="J0Q-3v-mYi" firstAttribute="leading" secondItem="IDd-dj-rlb" secondAttribute="leading" id="99C-v0-KHb"/>
<constraint firstAttribute="trailing" secondItem="J0Q-3v-mYi" secondAttribute="trailing" id="9UZ-xj-dko"/>
<constraint firstAttribute="bottom" secondItem="J0Q-3v-mYi" secondAttribute="bottom" id="ma4-3V-HrV"/>
<constraint firstItem="J0Q-3v-mYi" firstAttribute="top" secondItem="IDd-dj-rlb" secondAttribute="top" id="yal-op-ilq"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="VsR-2O-52e"/>
<connections>
<outlet property="collectionView" destination="J0Q-3v-mYi" id="Bgc-Bn-K8T"/>
<outlet property="collectionViewLayout" destination="xZ1-3i-1vR" id="Y07-LZ-q9e"/>
<segue destination="6yf-Ps-CJA" kind="show" identifier="showDetails" id="MyM-AG-rLN"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="MVB-v7-qj7" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1854" y="-662"/>
</scene>
<!--Anime Details View Controller-->
<scene sceneID="AiH-fe-1fY">
<objects>
<viewController id="nkV-Ie-GtD" customClass="AnimeDetailsViewController" customModule="PopcornTime" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="QRb-xd-WiJ"/>
<viewControllerLayoutGuide type="bottom" id="wed-jV-2Ok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="RxB-Ov-N2l">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="kC7-OM-puy">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="ozd-IJ-kA9" customClass="StratchyHeaderLayout" customModule="PopcornTime" customModuleProvider="target">
<size key="itemSize" width="50" height="50"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="5" minY="15" maxX="5" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="EpisodeCell" id="X2h-M0-N3e">
<rect key="frame" x="5" y="79" width="50" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" red="1" green="0.60942627989999998" blue="0.53172264930000002" alpha="1" colorSpace="calibratedRGB"/>
</collectionViewCell>
</cells>
</collectionView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="kC7-OM-puy" firstAttribute="top" secondItem="QRb-xd-WiJ" secondAttribute="bottom" id="7Pu-By-DOS"/>
<constraint firstAttribute="bottom" secondItem="kC7-OM-puy" secondAttribute="bottom" id="Ecj-IN-zJN"/>
<constraint firstItem="kC7-OM-puy" firstAttribute="top" secondItem="RxB-Ov-N2l" secondAttribute="topMargin" id="He9-Cj-d5V"/>
<constraint firstItem="kC7-OM-puy" firstAttribute="leading" secondItem="RxB-Ov-N2l" secondAttribute="leading" id="akD-31-hvB"/>
<constraint firstAttribute="trailing" secondItem="kC7-OM-puy" secondAttribute="trailing" id="cVC-Oz-LNb"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="7Pu-By-DOS"/>
</mask>
</variation>
</view>
<connections>
<outlet property="collectionView" destination="kC7-OM-puy" id="GQN-oz-xCA"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="17j-G1-ovt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2650" y="245"/>
</scene>
<!--Shows-->
<scene sceneID="Gqz-GQ-l19">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="Q7f-F1-kvd" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="Shows" image="ShowsIcon" id="Mo0-0H-mG1"/>
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" id="9UF-I7-giv">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="n2I-Pn-G6j" kind="relationship" relationship="rootViewController" id="2DI-dw-H8V"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="F6Y-qB-7Qf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1009" y="-662"/>
</scene>
<!--Anime-->
<scene sceneID="eYU-Fj-ZHe">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="3CV-c3-dhf" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="Anime" image="AnimeIcon" id="3HV-jo-aj6"/>
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" id="jM2-Oi-TbS">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="ADj-go-OwF" kind="relationship" relationship="rootViewController" id="6Pa-6X-tyy"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="eoB-VP-4Xl" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="986" y="253"/>
</scene>
<!--Loading View Controller-->
<scene sceneID="3uL-Yr-CLa">
<objects>
<viewController storyboardIdentifier="loadingViewController" id="bUe-YS-Bfo" customClass="LoadingViewController" customModule="PopcornTime" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="0OC-Vl-SlB"/>
<viewControllerLayoutGuide type="bottom" id="a3I-lb-Ai6"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ge5-4i-c4F">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3jf-HN-lVz">
<rect key="frame" x="0.0" y="20" width="600" height="580"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="Bbw-Ra-hAv">
<rect key="frame" x="0.0" y="0.0" width="600" height="580"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Eno-Np-eyT" userLabel="Container View">
<rect key="frame" x="170" y="150" width="260" height="280"/>
<subviews>
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" fixedFrame="YES" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="iQF-mn-H34">
<rect key="frame" x="20" y="85" width="220" height="2"/>
</progressView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="99%" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dyu-uI-e3s" userLabel="Progress Label">
<rect key="frame" x="55" y="95" width="150" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Speed: 1.5 MB/s" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZtX-Eh-3Ck" userLabel="Speed Label">
<rect key="frame" x="55" y="124" width="150" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Seeds: 20" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="odl-qX-n2O" userLabel="Seeds Label">
<rect key="frame" x="55" y="153" width="150" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Peers: 20" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HhI-Ni-8J8" userLabel="Peers Label">
<rect key="frame" x="55" y="182" width="150" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="OyV-xC-3a4" userLabel="Cancel Button">
<rect key="frame" x="95" y="226" width="70" height="30"/>
<state key="normal" title="Cancel">
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="cancelButtonPressed:" destination="bUe-YS-Bfo" eventType="touchUpInside" id="O0o-pv-5o4"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Downloading..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4bR-E2-MXi" userLabel="Status Label">
<rect key="frame" x="70" y="48" width="120" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="280" id="Abb-WO-CaK"/>
<constraint firstAttribute="width" constant="260" id="Azv-Bj-qqf"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Movie name - resolution" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="BFA-o4-k8D" userLabel="Title Label">
<rect key="frame" x="208" y="35" width="185" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="BFA-o4-k8D" firstAttribute="top" secondItem="Bbw-Ra-hAv" secondAttribute="top" constant="35" id="8GK-gX-IRy"/>
<constraint firstAttribute="centerX" secondItem="BFA-o4-k8D" secondAttribute="centerX" id="HQY-9H-dme"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="BFA-o4-k8D" secondAttribute="trailing" constant="5" id="TzI-CV-4j1"/>
<constraint firstAttribute="centerY" secondItem="Eno-Np-eyT" secondAttribute="centerY" id="euo-DO-0a6"/>
<constraint firstItem="BFA-o4-k8D" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Bbw-Ra-hAv" secondAttribute="leading" constant="5" id="f3S-uc-t2S"/>
<constraint firstAttribute="centerX" secondItem="Eno-Np-eyT" secondAttribute="centerX" id="fb1-QL-1Gt"/>
</constraints>
</view>
<blurEffect style="dark"/>
</visualEffectView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="3jf-HN-lVz" firstAttribute="leading" secondItem="Ge5-4i-c4F" secondAttribute="leadingMargin" id="49U-xF-EQh"/>
<constraint firstItem="a3I-lb-Ai6" firstAttribute="top" secondItem="3jf-HN-lVz" secondAttribute="bottom" id="B88-h4-23t"/>
<constraint firstAttribute="trailing" secondItem="3jf-HN-lVz" secondAttribute="trailing" id="Dek-k3-qex"/>
<constraint firstAttribute="trailingMargin" secondItem="3jf-HN-lVz" secondAttribute="trailing" id="Kog-8l-OhD"/>
<constraint firstItem="3jf-HN-lVz" firstAttribute="top" secondItem="0OC-Vl-SlB" secondAttribute="bottom" id="OXD-GG-H1V"/>
<constraint firstItem="3jf-HN-lVz" firstAttribute="leading" secondItem="Ge5-4i-c4F" secondAttribute="leading" id="QvQ-Gp-N6r"/>
<constraint firstItem="a3I-lb-Ai6" firstAttribute="top" secondItem="3jf-HN-lVz" secondAttribute="bottom" id="Ue0-UH-kxe"/>
<constraint firstItem="3jf-HN-lVz" firstAttribute="top" secondItem="0OC-Vl-SlB" secondAttribute="top" id="eb9-Zh-dmo"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="49U-xF-EQh"/>
<exclude reference="Kog-8l-OhD"/>
<exclude reference="OXD-GG-H1V"/>
<exclude reference="B88-h4-23t"/>
</mask>
</variation>
</view>
<connections>
<outlet property="peersLabel" destination="HhI-Ni-8J8" id="lJD-gK-S5n"/>
<outlet property="progressLabel" destination="Dyu-uI-e3s" id="11L-g5-Xlz"/>
<outlet property="progressView" destination="iQF-mn-H34" id="yxl-Nj-8wf"/>
<outlet property="seedsLabel" destination="odl-qX-n2O" id="fNP-Rd-afo"/>
<outlet property="speedLabel" destination="ZtX-Eh-3Ck" id="zu4-Oy-HuH"/>
<outlet property="statusLabel" destination="4bR-E2-MXi" id="Omi-rU-Gma"/>
<outlet property="titleLabel" destination="BFA-o4-k8D" id="H8g-9M-v8E"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="tWv-oB-zv8" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="166" y="1127"/>
</scene>
<!--Movies View Controller-->
<scene sceneID="w8j-0J-Eiu">
<objects>
<viewController id="MfG-VV-Dsq" customClass="MoviesViewController" customModule="PopcornTime" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="7zu-Fk-ucI"/>
<viewControllerLayoutGuide type="bottom" id="am9-41-5Cg"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="DTT-77-bFA">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="un7-QQ-yb5">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="dLT-p7-0kC">
<size key="itemSize" width="50" height="50"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells/>
</collectionView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="un7-QQ-yb5" secondAttribute="trailing" id="LMg-Wo-DNY"/>
<constraint firstAttribute="bottom" secondItem="un7-QQ-yb5" secondAttribute="bottom" id="MtX-vN-2gj"/>
<constraint firstItem="am9-41-5Cg" firstAttribute="top" secondItem="un7-QQ-yb5" secondAttribute="bottom" id="P8d-Zl-9ZP"/>
<constraint firstItem="un7-QQ-yb5" firstAttribute="leading" secondItem="DTT-77-bFA" secondAttribute="leading" id="cFz-Pk-Mmz"/>
<constraint firstItem="un7-QQ-yb5" firstAttribute="top" secondItem="DTT-77-bFA" secondAttribute="topMargin" id="eya-tL-5GQ"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="P8d-Zl-9ZP"/>
</mask>
</variation>
</view>
<navigationItem key="navigationItem" id="emw-Gq-25X"/>
<connections>
<outlet property="collectionView" destination="un7-QQ-yb5" id="WCU-nz-hXB"/>
<outlet property="collectionViewLayout" destination="dLT-p7-0kC" id="miO-uc-V55"/>
<segue destination="2vz-Nk-i3O" kind="show" identifier="showDetails" id="md6-Ka-eFz"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="s9J-gI-5Xf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1854" y="1127"/>
</scene>
<!--Movies-->
<scene sceneID="MVY-tk-2U8">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="laf-Ml-R54" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="Movies" image="MoviesIcon" id="hdd-ic-XlJ"/>
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" id="jlQ-Ip-DcT">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="MfG-VV-Dsq" kind="relationship" relationship="rootViewController" id="ZdR-AN-H6V"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="EPu-48-ilK" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1009" y="1127"/>
</scene>
<!--Show Details View Controller-->
<scene sceneID="jc0-r0-ie0">
<objects>
<viewController id="6yf-Ps-CJA" customClass="ShowDetailsViewController" customModule="PopcornTime" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Kkk-Uf-QOx"/>
<viewControllerLayoutGuide type="bottom" id="Gxb-eF-kn7"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="fSO-ml-ygC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="Cix-TO-yDb">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="QOJ-dh-oMS" customClass="StratchyHeaderLayout" customModule="PopcornTime" customModuleProvider="target">
<size key="itemSize" width="50" height="50"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="5" minY="15" maxX="5" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="EpisodeCell" id="WYM-12-06H">
<rect key="frame" x="5" y="79" width="50" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" red="1" green="0.60942627989999998" blue="0.53172264930000002" alpha="1" colorSpace="calibratedRGB"/>
</collectionViewCell>
</cells>
</collectionView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="Cix-TO-yDb" secondAttribute="trailing" id="1Ke-bE-sbv"/>
<constraint firstItem="Cix-TO-yDb" firstAttribute="top" secondItem="fSO-ml-ygC" secondAttribute="topMargin" id="Fg7-jg-DlH"/>
<constraint firstItem="Cix-TO-yDb" firstAttribute="leading" secondItem="fSO-ml-ygC" secondAttribute="leading" id="Gbz-4s-5ox"/>
<constraint firstItem="Cix-TO-yDb" firstAttribute="bottom" secondItem="fSO-ml-ygC" secondAttribute="bottomMargin" id="uhG-Ot-HZh"/>
</constraints>
</view>
<connections>
<outlet property="collectionView" destination="Cix-TO-yDb" id="YUl-P7-ImU"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="5nP-va-hCS" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2650" y="-662"/>
</scene>
<!--Movie Details View Controller-->
<scene sceneID="CbQ-5W-veU">
<objects>
<viewController id="2vz-Nk-i3O" customClass="MovieDetailsViewController" customModule="PopcornTime" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Cp9-yw-pA3"/>
<viewControllerLayoutGuide type="bottom" id="3zw-Wx-sxv"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ybe-E9-ZTf">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="FOJ-U6-lFA">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="dvR-y0-Sip" customClass="StratchyHeaderLayout" customModule="PopcornTime" customModuleProvider="target">
<size key="itemSize" width="50" height="50"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="5" minY="15" maxX="5" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="EpisodeCell" id="8rK-gB-HyN">
<rect key="frame" x="5" y="79" width="50" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" red="1" green="0.60942627989999998" blue="0.53172264930000002" alpha="1" colorSpace="calibratedRGB"/>
</collectionViewCell>
</cells>
</collectionView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="FOJ-U6-lFA" firstAttribute="top" secondItem="Cp9-yw-pA3" secondAttribute="bottom" id="Imy-I5-tBP"/>
<constraint firstItem="FOJ-U6-lFA" firstAttribute="top" secondItem="Ybe-E9-ZTf" secondAttribute="topMargin" id="MU0-Fg-HLk"/>
<constraint firstAttribute="bottom" secondItem="FOJ-U6-lFA" secondAttribute="bottom" id="gRn-c0-JpL"/>
<constraint firstAttribute="trailing" secondItem="FOJ-U6-lFA" secondAttribute="trailing" id="n2p-fc-1bd"/>
<constraint firstItem="FOJ-U6-lFA" firstAttribute="leading" secondItem="Ybe-E9-ZTf" secondAttribute="leading" id="xGL-To-8Fe"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="Imy-I5-tBP"/>
</mask>
</variation>
</view>
<connections>
<outlet property="collectionView" destination="FOJ-U6-lFA" id="nbx-ij-BiF"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="hi7-OG-RrF" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2650" y="1127"/>
</scene>
<!--Settings-->
<scene sceneID="bem-lN-RrM">
<objects>
<viewController id="PsM-1R-DZR" customClass="SettingsViewController" customModule="PopcornTime" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="DZI-mU-Mb1"/>
<viewControllerLayoutGuide type="bottom" id="EN2-Q0-mCJ"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="f1e-yA-mmT">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="10" sectionFooterHeight="10" translatesAutoresizingMaskIntoConstraints="NO" id="09A-zd-jWh">
<rect key="frame" x="0.0" y="0.0" width="600" height="556"/>
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="1" colorSpace="calibratedRGB"/>
<connections>
<outlet property="dataSource" destination="PsM-1R-DZR" id="LkZ-4H-AMh"/>
<outlet property="delegate" destination="PsM-1R-DZR" id="hXZ-yB-VQR"/>
</connections>
</tableView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="09A-zd-jWh" firstAttribute="bottom" secondItem="EN2-Q0-mCJ" secondAttribute="top" id="98Q-Ut-SCA"/>
<constraint firstAttribute="trailing" secondItem="09A-zd-jWh" secondAttribute="trailing" id="XWT-CL-eDQ"/>
<constraint firstItem="09A-zd-jWh" firstAttribute="leading" secondItem="f1e-yA-mmT" secondAttribute="leading" id="o1a-Ap-OIw"/>
<constraint firstItem="09A-zd-jWh" firstAttribute="top" secondItem="f1e-yA-mmT" secondAttribute="top" id="wC8-xJ-bK0"/>
</constraints>
</view>
<toolbarItems/>
<navigationItem key="navigationItem" title="Settings" id="yNe-p8-SDg">
<barButtonItem key="leftBarButtonItem" title="Done" id="9er-hO-ljh">
<connections>
<action selector="doneButtonTapped:" destination="PsM-1R-DZR" id="hyK-bZ-4Cs"/>
</connections>
</barButtonItem>
</navigationItem>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="c2O-RD-Dnk" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2642" y="-2339"/>
</scene>
<!--Favorites-->
<scene sceneID="Ajz-ma-oZc">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="ZTe-Ju-nKb" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="Favorites" image="FavoritesIcon" id="fW6-S6-lRJ"/>
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" id="vNU-OG-5CV">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="UYG-Ri-Xwt" kind="relationship" relationship="rootViewController" id="d95-MP-Deg"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="bPA-j1-sYU" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1009" y="-1583"/>
</scene>
<!--Favorites View Controller-->
<scene sceneID="295-pu-uhc">
<objects>
<viewController id="UYG-Ri-Xwt" customClass="FavoritesViewController" customModule="PopcornTime" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="63E-PP-ohP"/>
<viewControllerLayoutGuide type="bottom" id="IpR-5f-yWF"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="M3M-4W-n7v">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="eI5-vY-Geb">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="wc9-WL-RHv">
<size key="itemSize" width="50" height="50"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells/>
</collectionView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="eI5-vY-Geb" firstAttribute="leading" secondItem="M3M-4W-n7v" secondAttribute="leading" id="Gcb-tq-ZVc"/>
<constraint firstAttribute="trailing" secondItem="eI5-vY-Geb" secondAttribute="trailing" id="Ltt-Ro-FiQ"/>
<constraint firstItem="eI5-vY-Geb" firstAttribute="top" secondItem="M3M-4W-n7v" secondAttribute="top" id="gg3-bx-fnL"/>
<constraint firstAttribute="bottom" secondItem="eI5-vY-Geb" secondAttribute="bottom" id="hoL-CT-C9N"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="MY5-kk-pyb">
<barButtonItem key="leftBarButtonItem" image="SettingsIcon" id="b18-US-aYw">
<connections>
<segue destination="laO-YO-bEr" kind="presentation" modalPresentationStyle="formSheet" id="MHB-oZ-cjC"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="collectionView" destination="eI5-vY-Geb" id="9bl-Ru-8eV"/>
<outlet property="collectionViewLayout" destination="wc9-WL-RHv" id="heF-Dp-lKu"/>
<segue destination="6yf-Ps-CJA" kind="show" identifier="showDetailsForFavoriteShow" id="yKj-Tm-VOw"/>
<segue destination="nkV-Ie-GtD" kind="show" identifier="showDetailsForFavoriteAnime" id="Qrp-vN-Wm3"/>
<segue destination="2vz-Nk-i3O" kind="show" identifier="showDetailsForFavoriteMovie" id="Wdb-6o-Fxi"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="OKd-O3-qVh" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1854" y="-1583"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="umC-OZ-DUF">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="laO-YO-bEr" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" id="mhs-Pw-TcL">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="PsM-1R-DZR" kind="relationship" relationship="rootViewController" id="Hga-lw-gLH"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="pJk-EZ-fTT" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1854" y="-2355"/>
</scene>
</scenes>
<resources>
<image name="AnimeIcon" width="30" height="30"/>
<image name="FavoritesIcon" width="30" height="30"/>
<image name="MoviesIcon" width="30" height="30"/>
<image name="SettingsIcon" width="30" height="30"/>
<image name="ShowsIcon" width="30" height="30"/>
</resources>
<inferredMetricsTieBreakers>
<segue reference="Qrp-vN-Wm3"/>
<segue reference="yKj-Tm-VOw"/>
<segue reference="Wdb-6o-Fxi"/>
</inferredMetricsTieBreakers>
</document>
================================================
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
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="15A178w" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="EpisodeCell" id="gTV-IL-0wX" customClass="EpisodeCell" customModule="PopcornTime" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="381" height="86"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="381" height="86"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hac-x6-d8V">
<rect key="frame" x="0.0" y="0.0" width="381" height="86"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rw3-FD-IDF">
<rect key="frame" x="8" y="33" width="365" height="20"/>
<animations/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailingMargin" secondItem="rw3-FD-IDF" secondAttribute="trailing" id="4JM-g6-1yg"/>
<constraint firstAttribute="centerY" secondItem="rw3-FD-IDF" secondAttribute="centerY" id="Xme-ZK-Q6S"/>
<constraint firstItem="rw3-FD-IDF" firstAttribute="leading" secondItem="hac-x6-d8V" secondAttribute="leadingMargin" id="m7b-qe-tLL"/>
</constraints>
</view>
</subviews>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<animations/>
<color key="backgroundColor" red="0.29803922772407532" green="0.29803922772407532" blue="0.29803922772407532" alpha="0.45000000000000001" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="hac-x6-d8V" secondAttribute="trailing" id="3tn-e1-whc"/>
<constraint firstItem="hac-x6-d8V" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="ZQU-6d-HeS"/>
<constraint firstItem="hac-x6-d8V" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="cVo-K4-VEE"/>
<constraint firstAttribute="bottom" secondItem="hac-x6-d8V" secondAttribute="bottom" id="ief-JJ-Ktm"/>
</constraints>
<size key="customSize" width="381" height="86"/>
<connections>
<outlet property="titleLabel" destination="rw3-FD-IDF" id="8M1-8P-KLo"/>
</connections>
<point key="canvasLocation" x="340.5" y="236"/>
</collectionViewCell>
</objects>
</document>
================================================
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
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7702" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7701"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="MoreShowsCell" id="gTV-IL-0wX" customClass="MoreShowsCollectionViewCell" customModule="PopcornTime" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="228" height="254"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="228" height="254"/>
<subviews>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="Iyp-NF-gbb">
<rect key="frame" x="96" y="108" width="37" height="37"/>
</activityIndicatorView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<constraints>
<constraint firstAttribute="centerY" secondItem="Iyp-NF-gbb" secondAttribute="centerY" id="LVh-dH-GmR"/>
<constraint firstAttribute="centerX" secondItem="Iyp-NF-gbb" secondAttribute="centerX" id="QL8-3z-U1F"/>
</constraints>
<size key="customSize" width="228" height="254"/>
<point key="canvasLocation" x="232" y="359"/>
</collectionViewCell>
</objects>
</document>
================================================
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
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7702" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7701"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionReusableView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="U6b-Vx-4bR" customClass="SeasonHeader" customModule="PopcornTime" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bkC-4d-qdf" userLabel="container">
<rect key="frame" x="0.0" y="0.0" width="320" height="50"/>
<subviews>
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
SYMBOL INDEX (2 symbols across 2 files)
FILE: PopcornTime/Models/PTAPIManager.h
type PTItemTypeMovie (line 16) | typedef NS_ENUM(NSInteger, PTItemType) {
FILE: PopcornTime/Models/PTTorrentStreamer.h
type PTTorrentStatus (line 11) | typedef struct {
Condensed preview — 66 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (390K chars).
[
{
"path": ".gitignore",
"chars": 1067,
"preview": "# Created by https://www.gitignore.io\n\n### OSX ###\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n"
},
{
"path": "Podfile",
"chars": 435,
"preview": "# Uncomment this line to define a global platform for your project\n# platform :ios, '8.0'\ninhibit_all_warnings!\n\nsource "
},
{
"path": "PopcornTime/Controllers/AnimeDetailsViewController.swift",
"chars": 2003,
"preview": "//\n// ShowDetailsViewController.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/13/15.\n// Copyright (c) 2015 P"
},
{
"path": "PopcornTime/Controllers/AnimeViewController.swift",
"chars": 1730,
"preview": "//\n// MoviesViewController.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/15/15.\n// Copyright (c) 2015 Popcor"
},
{
"path": "PopcornTime/Controllers/BarHidingViewController.swift",
"chars": 921,
"preview": "//\n// BarHidingViewController.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/13/15.\n// Copyright (c) 2015 Pop"
},
{
"path": "PopcornTime/Controllers/BaseCollectionViewController.swift",
"chars": 6513,
"preview": "//\n// ShowsCollectionViewController.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/8/15.\n// Copyright (c) 201"
},
{
"path": "PopcornTime/Controllers/BaseDetailsViewController.swift",
"chars": 13139,
"preview": "//\n// BaseDetailsViewController.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 3/21/15.\n// Copyright (c) "
},
{
"path": "PopcornTime/Controllers/ColorfullTabBarController.swift",
"chars": 1757,
"preview": "//\n// ColorfullTabBarController.swift\n// PopcornTime\n//\n// Created by Andrew K. on 4/10/15.\n// Copyright (c) 2015 P"
},
{
"path": "PopcornTime/Controllers/FavoritesViewController.swift",
"chars": 2493,
"preview": "//\n// FavoritesViewController.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 3/13/15.\n// Copyright (c) 20"
},
{
"path": "PopcornTime/Controllers/LoadingViewController.swift",
"chars": 2473,
"preview": "//\n// LoadingViewController.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 3/15/15.\n// Copyright (c) 2015"
},
{
"path": "PopcornTime/Controllers/MovieDetailsViewController.swift",
"chars": 1988,
"preview": "//\n// ShowDetailsViewController.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/13/15.\n// Copyright (c) 2015 P"
},
{
"path": "PopcornTime/Controllers/MoviesViewController.swift",
"chars": 1777,
"preview": "//\n// MoviesViewController.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/19/15.\n// Copyright (c) 2015 Popcor"
},
{
"path": "PopcornTime/Controllers/OAuthViewController.swift",
"chars": 2091,
"preview": "//\n// WebViewController.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 3/15/15.\n// Copyright (c) 2015 Pop"
},
{
"path": "PopcornTime/Controllers/PagedViewController.swift",
"chars": 6690,
"preview": "//\n// PagedViewController.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/19/15.\n// Copyright (c) 2015 Popcorn"
},
{
"path": "PopcornTime/Controllers/ParseViewController.swift",
"chars": 1822,
"preview": "//\n// ParseViewController.swift\n//\n//\n// Created by Andriy K. on 6/22/15.\n//\n//\n\nimport UIKit\n\nclass ParseViewControll"
},
{
"path": "PopcornTime/Controllers/SettingsViewController.swift",
"chars": 2321,
"preview": "//\n// SettingsViewController.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 4/4/15.\n// Copyright (c) 2015"
},
{
"path": "PopcornTime/Controllers/ShowDetailsViewController.swift",
"chars": 2179,
"preview": "//\n// ShowDetailsViewController.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/13/15.\n// Copyright (c) 2015 P"
},
{
"path": "PopcornTime/Controllers/ShowsViewController.swift",
"chars": 1745,
"preview": "//\n// TVSeriesShowsViewController.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/9/15.\n// Copyright (c) 2015 "
},
{
"path": "PopcornTime/Info.plist",
"chars": 1746,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "PopcornTime/Models/APIManager.swift",
"chars": 5323,
"preview": "//\n// APIManager.swift\n// popcornTime\n//\n// Created by Danylo Kostyshyn on 3/13/15.\n// Copyright (c) 2015 Danylo Kos"
},
{
"path": "PopcornTime/Models/Anime.swift",
"chars": 4194,
"preview": "//\n// Anime.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 3/19/15.\n// Copyright (c) 2015 PopcornTime. Al"
},
{
"path": "PopcornTime/Models/AppDelegate.swift",
"chars": 557,
"preview": "//\n// AppDelegate.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 3/13/15.\n// Copyright (c) 2015 PopcornTi"
},
{
"path": "PopcornTime/Models/BaseStructures.swift",
"chars": 525,
"preview": "//\n// Video.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 3/21/15.\n// Copyright (c) 2015 PopcornTime. Al"
},
{
"path": "PopcornTime/Models/BasicInfo.swift",
"chars": 2382,
"preview": "//\n// BasicInfo.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 3/19/15.\n// Copyright (c) 2015 PopcornTime"
},
{
"path": "PopcornTime/Models/DataManager.swift",
"chars": 2822,
"preview": "//\n// DataManager.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 3/24/15.\n// Copyright (c) 2015 PopcornTi"
},
{
"path": "PopcornTime/Models/Extensions.swift",
"chars": 422,
"preview": "//\n// UIImage+PopcornTime.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 3/30/15.\n// Copyright (c) 2015 P"
},
{
"path": "PopcornTime/Models/Image.swift",
"chars": 1053,
"preview": "//\n// File.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 3/21/15.\n// Copyright (c) 2015 PopcornTime. All"
},
{
"path": "PopcornTime/Models/ImageProvider.swift",
"chars": 785,
"preview": "//\n// ImageProvider.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/10/15.\n// Copyright (c) 2015 PopcornTime. "
},
{
"path": "PopcornTime/Models/Movie.swift",
"chars": 1868,
"preview": "//\n// Movie.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 3/19/15.\n// Copyright (c) 2015 PopcornTime. Al"
},
{
"path": "PopcornTime/Models/PTAPIManager.h",
"chars": 1719,
"preview": "//\n// PTAPIManager.h\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 2/25/15.\n// Copyright (c) 2015 PopcornTime."
},
{
"path": "PopcornTime/Models/PTAPIManager.m",
"chars": 16049,
"preview": "//\n// PTAPIManager.m\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 2/25/15.\n// Copyright (c) 2015 PopcornTime."
},
{
"path": "PopcornTime/Models/PTTorrentStreamer.h",
"chars": 969,
"preview": "//\n// PTTorrentStreamer.h\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 2/23/15.\n// Copyright (c) 2015 Popcorn"
},
{
"path": "PopcornTime/Models/PTTorrentStreamer.mm",
"chars": 13481,
"preview": "//\n// PTTorrentStreamer.m\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 2/23/15.\n// Copyright (c) 2015 Popcorn"
},
{
"path": "PopcornTime/Models/ParseManager.swift",
"chars": 8857,
"preview": "//\n// ParseManager.swift\n// \n//\n// Created by Andriy K. on 6/23/15.\n//\n//\n\nimport UIKit\n\nclass ParseShowData: NSObjec"
},
{
"path": "PopcornTime/Models/Show.swift",
"chars": 4595,
"preview": "//\n// Show.swift\n// PopcornTime\n//\n// Created by Danylo Kostyshyn on 3/19/15.\n// Copyright (c) 2015 PopcornTime. All"
},
{
"path": "PopcornTime/PopcornTime-Bridging-Header.h",
"chars": 325,
"preview": "//\n// Use this file to import your target's public headers that you would like to expose to Swift.\n//\n\n#import \"PTAPIMa"
},
{
"path": "PopcornTime/Resources/Images.xcassets/AnimeIcon.imageset/Contents.json",
"chars": 302,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "PopcornTime/Resources/Images.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1609,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"iphone\",\n \"size\" : \"20x20\",\n \"scale\" : \"2x\"\n },\n {\n \"idiom\""
},
{
"path": "PopcornTime/Resources/Images.xcassets/BigLogo.imageset/Contents.json",
"chars": 314,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "PopcornTime/Resources/Images.xcassets/Contents.json",
"chars": 62,
"preview": "{\n \"info\" : {\n \"version\" : 1,\n \"author\" : \"xcode\"\n }\n}"
},
{
"path": "PopcornTime/Resources/Images.xcassets/SubwayIconSet/AddToFavoritesIcon.imageset/Contents.json",
"chars": 308,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "PopcornTime/Resources/Images.xcassets/SubwayIconSet/FavoritesIcon.imageset/Contents.json",
"chars": 308,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "PopcornTime/Resources/Images.xcassets/SubwayIconSet/MoviesIcon.imageset/Contents.json",
"chars": 309,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "PopcornTime/Resources/Images.xcassets/SubwayIconSet/RemoveFromFavoritesIcon.imageset/Contents.json",
"chars": 308,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "PopcornTime/Resources/Images.xcassets/SubwayIconSet/SettingsIcon.imageset/Contents.json",
"chars": 309,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "PopcornTime/Resources/Images.xcassets/SubwayIconSet/ShowsIcon.imageset/Contents.json",
"chars": 309,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "PopcornTime/Resources/Launch Screen.xib",
"chars": 3621,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" versi"
},
{
"path": "PopcornTime/Resources/Main.storyboard",
"chars": 54116,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
},
{
"path": "PopcornTime/Views/EpisodeCell.swift",
"chars": 498,
"preview": "//\n// EpisodeCell.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/13/15.\n// Copyright (c) 2015 PopcornTime. Al"
},
{
"path": "PopcornTime/Views/EpisodeCell.xib",
"chars": 4291,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" versi"
},
{
"path": "PopcornTime/Views/MoreShowsCollectionViewCell.swift",
"chars": 336,
"preview": "//\n// MoreShowsCollectionViewCell.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/10/15.\n// Copyright (c) 2015"
},
{
"path": "PopcornTime/Views/MoreShowsCollectionViewCell.xib",
"chars": 2209,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" versi"
},
{
"path": "PopcornTime/Views/SeasonHeader.swift",
"chars": 299,
"preview": "//\n// SeasonHeader.swift\n// PopcornTime\n//\n// Created by Andrew K. on 4/14/15.\n// Copyright (c) 2015 PopcornTime. A"
},
{
"path": "PopcornTime/Views/SeasonHeader.xib",
"chars": 3871,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" versi"
},
{
"path": "PopcornTime/Views/ShowCollectionViewCell.swift",
"chars": 710,
"preview": "//\n// ShowCollectionViewCell.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/8/15.\n// Copyright (c) 2015 Popco"
},
{
"path": "PopcornTime/Views/ShowCollectionViewCell.xib",
"chars": 6891,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" versi"
},
{
"path": "PopcornTime/Views/StratchyHeader.swift",
"chars": 3205,
"preview": "//\n// StratchyHeader.swift\n// PopcornTime\n//\n// Created by Andrew K. on 4/6/15.\n// Copyright (c) 2015 PopcornTime. "
},
{
"path": "PopcornTime/Views/StratchyHeader.xib",
"chars": 16946,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" versi"
},
{
"path": "PopcornTime/Views/StratchyHeaderLayout.swift",
"chars": 3565,
"preview": "//\n// StratchyHeaderLayout.swift\n// PopcornTime\n//\n// Created by Andrew K. on 3/13/15.\n// Copyright (c) 2015 Popcor"
},
{
"path": "PopcornTime.xcodeproj/project.pbxproj",
"chars": 47349,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "PopcornTime.xcodeproj/xcshareddata/xcschemes/PopcornTime.xcscheme",
"chars": 3351,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"0820\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "README.md",
"chars": 866,
"preview": "## PopcornTime for iOS\n\n[]("
},
{
"path": "Thirdparties/VLCKit/Dropin-Player/VDLPlaybackViewController.h",
"chars": 2746,
"preview": "/* Copyright (c) 2013, Felix Paul Kühne and VideoLAN\n * All rights reserved.\n *\n * Redistribution and use in source and "
},
{
"path": "Thirdparties/VLCKit/Dropin-Player/VDLPlaybackViewController.m",
"chars": 12829,
"preview": "/* Copyright (c) 2013, Felix Paul Kühne and VideoLAN\n * All rights reserved.\n *\n * Redistribution and use in source and "
},
{
"path": "Thirdparties/VLCKit/Dropin-Player/VDLPlaybackViewController.xib",
"chars": 34012,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<archive type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" version=\"8.00\">\n\t<data"
},
{
"path": "popcorntime_api.paw",
"chars": 33852,
"preview": "<?xml version=\"1.0\" standalone=\"no\"?>\n<!DOCTYPE database SYSTEM \"file:///System/Library/DTDs/CoreData.dtd\">\n\n<database>\n"
}
]
About this extraction
This page contains the full source code of the danylokostyshyn/popcorntime-ios GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 66 files (352.1 KB), approximately 93.3k tokens, and a symbol index with 2 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.