├── .gitignore ├── LICENSE ├── Podfile ├── Podverse.xcodeproj └── project.pbxproj ├── Podverse ├── AboutPlayingItemViewController.swift ├── AboutViewController.swift ├── AddToPlaylistViewController.swift ├── AppDelegate.swift ├── Array+rearrange.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── 114.png │ │ ├── 120-1.png │ │ ├── 120.png │ │ ├── 180.png │ │ ├── 29.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 80.png │ │ ├── 87.png │ │ ├── Contents.json │ │ └── icon-1024x1024.png │ ├── Blank52.imageset │ │ ├── Blank52.png │ │ ├── Blank52@2x.png │ │ └── Contents.json │ ├── Contents.json │ ├── LaunchImage.launchimage │ │ ├── 1125x2436.png │ │ ├── 1242x2208.png │ │ ├── 2208x1242.png │ │ ├── 320x480.png │ │ ├── 640x1136-1.png │ │ ├── 640x1136.png │ │ ├── 640x960-1.png │ │ ├── 640x960.png │ │ ├── 750x1334.png │ │ └── Contents.json │ ├── PodverseIcon.imageset │ │ ├── Contents.json │ │ ├── PodverseIcon.png │ │ ├── PodverseIcon@2x.png │ │ └── PodverseIcon@3x.png │ ├── SliderClipPosition.imageset │ │ ├── Contents.json │ │ └── slider-clip-position.png │ ├── SliderCurrentPosition.imageset │ │ ├── Contents.json │ │ └── slider-current-position.png │ ├── Tab-Downloads.imageset │ │ └── Contents.json │ ├── Tab-Find.imageset │ │ └── Contents.json │ ├── Tab-Podcasts.imageset │ │ └── Contents.json │ ├── add.imageset │ │ ├── Contents.json │ │ ├── add.png │ │ ├── add@2x.png │ │ └── add@3x.png │ ├── auto-dl.imageset │ │ ├── Contents.json │ │ ├── auto-dl.png │ │ ├── auto-dl@2x.png │ │ └── auto-dl@3x.png │ ├── back15.imageset │ │ ├── Contents.json │ │ ├── back15-48px.png │ │ ├── back15-72px.png │ │ └── back15-96px.png │ ├── banner-auth0.imageset │ │ ├── Contents.json │ │ └── banner-auth0.png │ ├── banner.imageset │ │ ├── Contents.json │ │ ├── banner.png │ │ ├── banner@2x.png │ │ └── banner@3x.png │ ├── clear.imageset │ │ ├── Contents.json │ │ ├── clear.png │ │ ├── clear@2x.png │ │ └── clear@3x.png │ ├── clip.imageset │ │ ├── Contents.json │ │ ├── clip.png │ │ ├── clip@2x.png │ │ └── clip@3x.png │ ├── cloud.imageset │ │ ├── Contents.json │ │ ├── cloud.png │ │ ├── cloud@2x.png │ │ └── cloud@3x.png │ ├── drag │ │ ├── Contents.json │ │ ├── animation-1.dataset │ │ │ ├── Contents.json │ │ │ └── animation-1.tiff │ │ ├── animation-10.dataset │ │ │ ├── Contents.json │ │ │ └── animation-10.tiff │ │ ├── animation-11.dataset │ │ │ ├── Contents.json │ │ │ └── animation-11.tiff │ │ ├── animation-12.dataset │ │ │ ├── Contents.json │ │ │ └── animation-12.tiff │ │ ├── animation-13.dataset │ │ │ ├── Contents.json │ │ │ └── animation-13.tiff │ │ ├── animation-14.dataset │ │ │ ├── Contents.json │ │ │ └── animation-14.tiff │ │ ├── animation-15.dataset │ │ │ ├── Contents.json │ │ │ └── animation-15.tiff │ │ ├── animation-16.dataset │ │ │ ├── Contents.json │ │ │ └── animation-16.tiff │ │ ├── animation-17.dataset │ │ │ ├── Contents.json │ │ │ └── animation-17.tiff │ │ ├── animation-18.dataset │ │ │ ├── Contents.json │ │ │ └── animation-18.tiff │ │ ├── animation-19.dataset │ │ │ ├── Contents.json │ │ │ └── animation-19.tiff │ │ ├── animation-2.dataset │ │ │ ├── Contents.json │ │ │ └── animation-2.tiff │ │ ├── animation-20.dataset │ │ │ ├── Contents.json │ │ │ └── animation-20.tiff │ │ ├── animation-3.dataset │ │ │ ├── Contents.json │ │ │ └── animation-3.tiff │ │ ├── animation-4.dataset │ │ │ ├── Contents.json │ │ │ └── animation-4.tiff │ │ ├── animation-5.dataset │ │ │ ├── Contents.json │ │ │ └── animation-5.tiff │ │ ├── animation-6.dataset │ │ │ ├── Contents.json │ │ │ └── animation-6.tiff │ │ ├── animation-7.dataset │ │ │ ├── Contents.json │ │ │ └── animation-7.tiff │ │ ├── animation-8.dataset │ │ │ ├── Contents.json │ │ │ └── animation-8.tiff │ │ └── animation-9.dataset │ │ │ ├── Contents.json │ │ │ └── animation-9.tiff │ ├── forward15.imageset │ │ ├── Contents.json │ │ ├── forward15-48px.png │ │ ├── forward15-72px.png │ │ └── forward15-96px.png │ ├── icon_grab.imageset │ │ ├── Contents.json │ │ ├── icon_grab@2x.png │ │ └── icon_grab@3x.png │ ├── pause.imageset │ │ ├── Contents.json │ │ ├── pause.png │ │ ├── pause@2x.png │ │ └── pause@3x.png │ ├── play.imageset │ │ ├── Contents.json │ │ ├── play.png │ │ ├── play@2x.png │ │ └── play@3x.png │ ├── playerror.imageset │ │ ├── Contents.json │ │ ├── playerror.png │ │ ├── playerror@2x.png │ │ └── playerror@3x.png │ ├── skipbackward.imageset │ │ ├── Contents.json │ │ ├── skipbackward.png │ │ ├── skipbackward@2x.png │ │ └── skipbackward@3x.png │ ├── skipforward.imageset │ │ ├── Contents.json │ │ ├── skipforward.png │ │ ├── skipforward@2x.png │ │ └── skipforward@3x.png │ ├── speed05x.imageset │ │ ├── 0_5x_48px.png │ │ ├── 0_5x_72px.png │ │ └── Contents.json │ ├── speed075x.imageset │ │ ├── 0_75x_48px.png │ │ ├── 0_75x_72px.png │ │ └── Contents.json │ ├── speed125x.imageset │ │ ├── 1_25x_48px.png │ │ ├── 1_25x_72px.png │ │ └── Contents.json │ ├── speed15x.imageset │ │ ├── 1_5x_48px.png │ │ ├── 1_5x_72px.png │ │ └── Contents.json │ ├── speed1x.imageset │ │ ├── 1x-72px.png │ │ └── Contents.json │ ├── speed2x.imageset │ │ ├── 2x_48px.png │ │ ├── 2x_72px.png │ │ └── Contents.json │ ├── tab-clips.imageset │ │ ├── Contents.json │ │ ├── tab-clips.png │ │ ├── tab-clips@2x.png │ │ └── tab-clips@3x.png │ ├── tab-downloads.imageset │ │ ├── tab-downloads.png │ │ ├── tab-downloads@2x.png │ │ └── tab-downloads@3x.png │ ├── tab-find.imageset │ │ ├── tab-find.png │ │ ├── tab-find@2x.png │ │ └── tab-find@3x.png │ ├── tab-more.imageset │ │ ├── Contents.json │ │ ├── tab-more.png │ │ ├── tab-more@2x.png │ │ └── tab-more@3x.png │ └── tab-podcasts.imageset │ │ ├── tab-podcasts.png │ │ ├── tab-podcasts@2x.png │ │ └── tab-podcasts@3x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── ClipEpisodeTableViewCell.swift ├── ClipPodcastTableViewCell.swift ├── ClipTableViewCell.swift ├── ClipsListContainerViewController.swift ├── ClipsTableViewController.swift ├── Constants.swift ├── CoreDataHelper.swift ├── Data+ResizeImageData.swift ├── Date+FeedsDate.swift ├── Date+Formatters.swift ├── DeletingPodcasts.swift ├── DownloadTableViewCell.swift ├── DownloadingEpisode.swift ├── DownloadingEpisodeList.swift ├── DownloadsTableViewController.swift ├── Episode.swift ├── EpisodeTableViewCell.swift ├── EpisodeTableViewController.swift ├── EpisodesTableViewController.swift ├── EpisodesTableViewController.swift.orig ├── FiltersTableHeaderView.swift ├── FindBrowseGroupsViewController.swift ├── FindBrowsePodcastsViewController.swift ├── FindSearchTableViewController.swift ├── FindTableViewController.swift ├── Info.plist ├── Int64+MediaPlayerString.swift ├── LoginViewController.swift ├── MakeClipTimeViewController.swift ├── MediaPlayerViewController.swift ├── MediaRef.swift ├── MoreTableViewController.swift ├── NowPlayingBar.swift ├── NowPlayingBar.xib ├── PVAuth.swift ├── PVDeleter.swift ├── PVDownloader.swift ├── PVFeedParser.swift ├── PVMediaPlayer.swift ├── PVPlayerHistoryManager.swift ├── PVReachability.swift ├── PVSubscriber.swift ├── PVTimeHelpers.swift ├── PVViewController.swift ├── ParsingPodcasts.swift ├── PlayerHistoryItem.swift ├── Playlist.swift ├── PlaylistDetailTableViewCell.swift ├── PlaylistDetailTableViewController.swift ├── PlaylistTableViewCell.swift ├── PlaylistsTableViewController.swift ├── Podcast.swift ├── PodcastSearchResultTableViewCell.swift ├── PodcastTableViewCell.swift ├── PodcastsTableViewController.swift ├── Podverse.entitlements ├── SearchCategory.swift ├── SearchEpisode.swift ├── SearchNetwork.swift ├── SearchPodcast.swift ├── SearchPodcastViewController.swift ├── SettingsTableViewController.swift ├── String+CharacterManipulation.swift ├── String+ConvertPlaybackTimeToUrlSchemeElements.swift ├── String+Formatters.swift ├── String+MediaPlayerTimeToSeconds.swift ├── String+RemoveHTMLCharacters.swift ├── String+Utilities.swift ├── Supporting Libraries │ └── OAuth2RetryHandler.swift ├── SyncablePodcast.swift ├── UITabbarController+PlayerView.swift ├── UITableView+NotAvailableView.swift ├── UIViewController+AllowCellularDataDownloadsAlert.swift ├── UIViewController+InternetRequiredAlert.swift ├── UIViewController+ShowToast.swift ├── URL+Converters.swift ├── URL+RemoteSize.swift ├── URL+getQueryParamValue.swift ├── Utilities.swift ├── WebKitViewController.swift ├── podverse-Bridging-Header.h └── podverse.xcdatamodeld │ └── podverse.xcdatamodel │ └── contents ├── PodverseTests ├── Info.plist └── PodverseTests.swift ├── PodverseUITests ├── Info.plist └── PodverseUITests.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | .DS_Store 20 | 21 | ## Other 22 | *.moved-aside 23 | *.xcuserstate 24 | 25 | ## Other - mitch uses this directory for local archives 26 | test_builds/ 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.dSYM.zip 31 | *.dSYM 32 | *.xcworkspace 33 | .build/ 34 | Auth0.plist 35 | 36 | # CocoaPods 37 | Pods/ 38 | Podfile.lock 39 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '11' 3 | source 'https://github.com/CocoaPods/Specs.git' 4 | 5 | target 'Podverse' do 6 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 7 | use_frameworks! 8 | 9 | # Pods for Podverse 10 | pod 'Alamofire', '~> 4.0' 11 | pod 'Lock', '~> 2.3' 12 | pod 'Auth0', '~> 1.6' 13 | pod 'ReachabilitySwift', '~> 3' 14 | pod 'p2.OAuth2', '~> 4.0.0' 15 | pod 'StreamingKit', :git => 'https://github.com/podverse/StreamingKit.git', :branch => 'master' 16 | pod 'SDWebImage', '~> 4.0' 17 | pod 'FeedKit', '~> 6.0' 18 | pod 'TaskQueue' 19 | pod 'Fabric' 20 | pod 'Crashlytics' 21 | 22 | target 'PodverseTests' do 23 | inherit! :search_paths 24 | # Pods for testing 25 | end 26 | 27 | target 'PodverseUITests' do 28 | inherit! :search_paths 29 | # Pods for testing 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /Podverse/AboutPlayingItemViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutPlayingItemViewController.swift 3 | // Podverse 4 | // 5 | // Created by Creon Creonopoulos on 7/14/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AboutPlayingItemViewController: UIViewController, UIWebViewDelegate { 12 | 13 | let pvMediaPlayer = PVMediaPlayer.shared 14 | let playerHistoryManager = PlayerHistory.manager 15 | 16 | @IBOutlet weak var webView: UIWebView! 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | addObservers() 22 | 23 | loadWebView() 24 | 25 | self.view.backgroundColor = UIColor.black 26 | self.webView.delegate = self 27 | } 28 | 29 | override func viewWillLayoutSubviews() { 30 | super.viewWillLayoutSubviews() 31 | self.webView.scrollView.contentInset = UIEdgeInsets.zero; 32 | } 33 | 34 | deinit { 35 | removeObservers() 36 | } 37 | 38 | fileprivate func addObservers() { 39 | NotificationCenter.default.addObserver(self, selector: #selector(loadWebView), name: .hideClipData, object: nil) 40 | NotificationCenter.default.addObserver(self, selector: #selector(loadWebView), name: .clipUpdated, object: nil) 41 | NotificationCenter.default.addObserver(self, selector: #selector(loadWebView), name: .clipDeleted, object: nil) 42 | } 43 | 44 | fileprivate func removeObservers() { 45 | NotificationCenter.default.removeObserver(self, name: .hideClipData, object: nil) 46 | NotificationCenter.default.removeObserver(self, name: .clipUpdated, object: nil) 47 | NotificationCenter.default.removeObserver(self, name: .clipDeleted, object: nil) 48 | } 49 | 50 | @objc fileprivate func loadWebView() { 51 | 52 | if let item = pvMediaPlayer.nowPlayingItem { 53 | var text = "" 54 | 55 | if item.isClip() { 56 | if let title = item.clipTitle, !title.isEmpty { 57 | text += "" + title + "" + "

" 58 | } else { 59 | text += "Untitled clip" + "

" 60 | } 61 | 62 | if let time = item.readableStartAndEndTime() { 63 | text += "" + time + "" 64 | } 65 | 66 | if let userId = UserDefaults.standard.string(forKey: "userId"), userId == item.ownerId { 67 | text += "Edit Clip" 68 | } 69 | 70 | text += "



" 71 | } 72 | 73 | if let summary = item.episodeSummary, summary.trimmingCharacters(in: .whitespacesAndNewlines).count >= 1, let enrichedSummary = summary.convertPlaybackTimesToUrlSchemeElements() { 74 | text += enrichedSummary 75 | } else { 76 | text += kNoShowNotesMessage 77 | } 78 | 79 | self.webView.loadHTMLString(text.formatHtmlString(isWhiteBg: false), baseURL: nil) 80 | 81 | } 82 | 83 | } 84 | 85 | func showEditClip() { 86 | if !self.pvMediaPlayer.isDataAvailable { 87 | return 88 | } 89 | 90 | if !checkForConnectivity() { 91 | self.showInternetNeededAlertWithDescription(message: "You must be connected to the internet to edit clips.") 92 | return 93 | } 94 | 95 | if let item = self.playerHistoryManager.historyItems.first { 96 | self.navigationItem.backBarButtonItem = UIBarButtonItem(title:"Player", style:.plain, target:nil, action:nil) 97 | self.performSegue(withIdentifier: "Show Make Clip Time", sender: item) 98 | } 99 | } 100 | 101 | func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool { 102 | if navigationType == UIWebViewNavigationType.linkClicked { 103 | if let url = request.url, url.scheme == "podverse", let query = url.query { 104 | if query == "editClip" { 105 | DispatchQueue.main.async { 106 | self.showEditClip() 107 | } 108 | } else if query == "restartClip" { 109 | if let startTime = self.pvMediaPlayer.nowPlayingItem?.startTime { 110 | self.pvMediaPlayer.seek(toTime: Double(startTime)) 111 | } 112 | } else { 113 | let playbackTime = query.mediaPlayerTimeToSeconds() 114 | self.pvMediaPlayer.seek(toTime: Double(playbackTime)) 115 | } 116 | } else if let url = request.url { 117 | UIApplication.shared.open(url) 118 | } 119 | return false 120 | } 121 | return true 122 | } 123 | 124 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 125 | if segue.identifier == "Show Make Clip Time" { 126 | if let sender = sender as? PlayerHistoryItem, let makeClipTimeViewController = segue.destination as? MakeClipTimeViewController { 127 | makeClipTimeViewController.editingItem = sender 128 | } 129 | } 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /Podverse/AboutViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutViewController.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 7/3/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | 12 | class AboutViewController: PVViewController { 13 | 14 | @IBOutlet weak var loadingIndicator: UIActivityIndicatorView! 15 | @IBOutlet weak var webview: WKWebView! 16 | var requestUrl:URL? 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | self.webview.navigationDelegate = self 21 | if let url = self.requestUrl { 22 | self.webview.load(URLRequest(url: url)) 23 | self.webview.allowsBackForwardNavigationGestures = false 24 | } 25 | } 26 | 27 | override func viewWillAppear(_ animated: Bool) { 28 | self.tabBarController?.hidePlayerView() 29 | } 30 | 31 | override func viewWillDisappear(_ animated: Bool) { 32 | self.toggleNowPlayingBar() 33 | } 34 | } 35 | 36 | extension AboutViewController:WKNavigationDelegate { 37 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 38 | self.loadingIndicator.stopAnimating() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Podverse/Array+rearrange.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+rearrange.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 7/2/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array { // thanks Leo Dabus https://stackoverflow.com/a/36542411/2608858 12 | mutating func rearrange(from: Int, to: Int) { 13 | if (from == to) { return } 14 | precondition(indices.contains(from) && indices.contains(to), "invalid indexes") 15 | insert(remove(at: from), at: to) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/AppIcon.appiconset/120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/AppIcon.appiconset/120-1.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "29.png", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "58.png", 23 | "scale" : "2x" 24 | }, 25 | { 26 | "size" : "29x29", 27 | "idiom" : "iphone", 28 | "filename" : "87.png", 29 | "scale" : "3x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "80.png", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "size" : "40x40", 39 | "idiom" : "iphone", 40 | "filename" : "120-1.png", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "size" : "57x57", 45 | "idiom" : "iphone", 46 | "filename" : "57.png", 47 | "scale" : "1x" 48 | }, 49 | { 50 | "size" : "57x57", 51 | "idiom" : "iphone", 52 | "filename" : "114.png", 53 | "scale" : "2x" 54 | }, 55 | { 56 | "size" : "60x60", 57 | "idiom" : "iphone", 58 | "filename" : "120.png", 59 | "scale" : "2x" 60 | }, 61 | { 62 | "size" : "60x60", 63 | "idiom" : "iphone", 64 | "filename" : "180.png", 65 | "scale" : "3x" 66 | }, 67 | { 68 | "size" : "1024x1024", 69 | "idiom" : "ios-marketing", 70 | "filename" : "icon-1024x1024.png", 71 | "scale" : "1x" 72 | } 73 | ], 74 | "info" : { 75 | "version" : 1, 76 | "author" : "xcode" 77 | } 78 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/AppIcon.appiconset/icon-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/AppIcon.appiconset/icon-1024x1024.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/Blank52.imageset/Blank52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/Blank52.imageset/Blank52.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/Blank52.imageset/Blank52@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/Blank52.imageset/Blank52@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/Blank52.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Blank52.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Blank52@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/LaunchImage.launchimage/1125x2436.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/LaunchImage.launchimage/1125x2436.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/LaunchImage.launchimage/1242x2208.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/LaunchImage.launchimage/1242x2208.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/LaunchImage.launchimage/2208x1242.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/LaunchImage.launchimage/2208x1242.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/LaunchImage.launchimage/320x480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/LaunchImage.launchimage/320x480.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/LaunchImage.launchimage/640x1136-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/LaunchImage.launchimage/640x1136-1.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/LaunchImage.launchimage/640x1136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/LaunchImage.launchimage/640x1136.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/LaunchImage.launchimage/640x960-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/LaunchImage.launchimage/640x960-1.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/LaunchImage.launchimage/640x960.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/LaunchImage.launchimage/640x960.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/LaunchImage.launchimage/750x1334.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/LaunchImage.launchimage/750x1334.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "extent" : "full-screen", 5 | "idiom" : "iphone", 6 | "subtype" : "2436h", 7 | "filename" : "1125x2436.png", 8 | "minimum-system-version" : "11.0", 9 | "orientation" : "portrait", 10 | "scale" : "3x" 11 | }, 12 | { 13 | "extent" : "full-screen", 14 | "idiom" : "iphone", 15 | "subtype" : "736h", 16 | "filename" : "1242x2208.png", 17 | "minimum-system-version" : "8.0", 18 | "orientation" : "portrait", 19 | "scale" : "3x" 20 | }, 21 | { 22 | "extent" : "full-screen", 23 | "idiom" : "iphone", 24 | "subtype" : "736h", 25 | "filename" : "2208x1242.png", 26 | "minimum-system-version" : "8.0", 27 | "orientation" : "landscape", 28 | "scale" : "3x" 29 | }, 30 | { 31 | "extent" : "full-screen", 32 | "idiom" : "iphone", 33 | "subtype" : "667h", 34 | "filename" : "750x1334.png", 35 | "minimum-system-version" : "8.0", 36 | "orientation" : "portrait", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "portrait", 41 | "idiom" : "iphone", 42 | "filename" : "640x960.png", 43 | "extent" : "full-screen", 44 | "minimum-system-version" : "7.0", 45 | "scale" : "2x" 46 | }, 47 | { 48 | "extent" : "full-screen", 49 | "idiom" : "iphone", 50 | "subtype" : "retina4", 51 | "filename" : "640x1136.png", 52 | "minimum-system-version" : "7.0", 53 | "orientation" : "portrait", 54 | "scale" : "2x" 55 | }, 56 | { 57 | "orientation" : "portrait", 58 | "idiom" : "iphone", 59 | "filename" : "320x480.png", 60 | "extent" : "full-screen", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "orientation" : "portrait", 65 | "idiom" : "iphone", 66 | "filename" : "640x960-1.png", 67 | "extent" : "full-screen", 68 | "scale" : "2x" 69 | }, 70 | { 71 | "orientation" : "portrait", 72 | "idiom" : "iphone", 73 | "filename" : "640x1136-1.png", 74 | "extent" : "full-screen", 75 | "subtype" : "retina4", 76 | "scale" : "2x" 77 | } 78 | ], 79 | "info" : { 80 | "version" : 1, 81 | "author" : "xcode" 82 | } 83 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/PodverseIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "PodverseIcon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "PodverseIcon@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "PodverseIcon@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/PodverseIcon.imageset/PodverseIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/PodverseIcon.imageset/PodverseIcon.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/PodverseIcon.imageset/PodverseIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/PodverseIcon.imageset/PodverseIcon@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/PodverseIcon.imageset/PodverseIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/PodverseIcon.imageset/PodverseIcon@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/SliderClipPosition.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "slider-clip-position.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/SliderClipPosition.imageset/slider-clip-position.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/SliderClipPosition.imageset/slider-clip-position.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/SliderCurrentPosition.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "slider-current-position.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/SliderCurrentPosition.imageset/slider-current-position.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/SliderCurrentPosition.imageset/slider-current-position.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/Tab-Downloads.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab-downloads.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab-downloads@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab-downloads@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/Tab-Find.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab-find.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab-find@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab-find@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/Tab-Podcasts.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab-podcasts.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab-podcasts@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab-podcasts@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/add.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "add.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "add@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "add@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/add.imageset/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/add.imageset/add.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/add.imageset/add@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/add.imageset/add@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/add.imageset/add@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/add.imageset/add@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/auto-dl.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "auto-dl.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "auto-dl@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "auto-dl@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/auto-dl.imageset/auto-dl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/auto-dl.imageset/auto-dl.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/auto-dl.imageset/auto-dl@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/auto-dl.imageset/auto-dl@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/auto-dl.imageset/auto-dl@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/auto-dl.imageset/auto-dl@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/back15.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "back15-48px.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "back15-72px.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "back15-96px.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/back15.imageset/back15-48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/back15.imageset/back15-48px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/back15.imageset/back15-72px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/back15.imageset/back15-72px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/back15.imageset/back15-96px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/back15.imageset/back15-96px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/banner-auth0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "banner-auth0.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/banner-auth0.imageset/banner-auth0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/banner-auth0.imageset/banner-auth0.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/banner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "banner.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "banner@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "banner@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/banner.imageset/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/banner.imageset/banner.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/banner.imageset/banner@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/banner.imageset/banner@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/banner.imageset/banner@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/banner.imageset/banner@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/clear.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "clear.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "clear@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "clear@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/clear.imageset/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/clear.imageset/clear.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/clear.imageset/clear@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/clear.imageset/clear@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/clear.imageset/clear@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/clear.imageset/clear@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/clip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "clip.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "clip@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "clip@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/clip.imageset/clip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/clip.imageset/clip.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/clip.imageset/clip@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/clip.imageset/clip@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/clip.imageset/clip@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/clip.imageset/clip@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/cloud.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cloud.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "cloud@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "cloud@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/cloud.imageset/cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/cloud.imageset/cloud.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/cloud.imageset/cloud@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/cloud.imageset/cloud@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/cloud.imageset/cloud@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/cloud.imageset/cloud@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-1.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-1.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-1.dataset/animation-1.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-1.dataset/animation-1.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-10.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-10.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-10.dataset/animation-10.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-10.dataset/animation-10.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-11.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-11.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-11.dataset/animation-11.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-11.dataset/animation-11.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-12.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-12.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-12.dataset/animation-12.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-12.dataset/animation-12.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-13.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-13.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-13.dataset/animation-13.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-13.dataset/animation-13.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-14.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-14.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-14.dataset/animation-14.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-14.dataset/animation-14.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-15.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-15.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-15.dataset/animation-15.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-15.dataset/animation-15.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-16.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-16.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-16.dataset/animation-16.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-16.dataset/animation-16.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-17.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-17.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-17.dataset/animation-17.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-17.dataset/animation-17.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-18.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-18.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-18.dataset/animation-18.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-18.dataset/animation-18.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-19.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-19.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-19.dataset/animation-19.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-19.dataset/animation-19.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-2.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-2.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-2.dataset/animation-2.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-2.dataset/animation-2.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-20.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-20.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-20.dataset/animation-20.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-20.dataset/animation-20.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-3.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-3.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-3.dataset/animation-3.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-3.dataset/animation-3.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-4.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-4.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-4.dataset/animation-4.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-4.dataset/animation-4.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-5.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-5.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-5.dataset/animation-5.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-5.dataset/animation-5.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-6.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-6.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-6.dataset/animation-6.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-6.dataset/animation-6.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-7.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-7.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-7.dataset/animation-7.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-7.dataset/animation-7.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-8.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-8.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-8.dataset/animation-8.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-8.dataset/animation-8.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-9.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "animation-9.tiff" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/drag/animation-9.dataset/animation-9.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/drag/animation-9.dataset/animation-9.tiff -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/forward15.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "forward15-48px.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "forward15-72px.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "forward15-96px.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/forward15.imageset/forward15-48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/forward15.imageset/forward15-48px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/forward15.imageset/forward15-72px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/forward15.imageset/forward15-72px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/forward15.imageset/forward15-96px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/forward15.imageset/forward15-96px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/icon_grab.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon_grab@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "icon_grab@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/icon_grab.imageset/icon_grab@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/icon_grab.imageset/icon_grab@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/icon_grab.imageset/icon_grab@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/icon_grab.imageset/icon_grab@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/pause.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "pause.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "pause@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "pause@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/pause.imageset/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/pause.imageset/pause.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/pause.imageset/pause@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/pause.imageset/pause@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/pause.imageset/pause@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/pause.imageset/pause@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/play.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "play.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "play@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "play@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/play.imageset/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/play.imageset/play.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/play.imageset/play@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/play.imageset/play@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/play.imageset/play@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/play.imageset/play@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/playerror.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "playerror.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "playerror@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "playerror@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/playerror.imageset/playerror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/playerror.imageset/playerror.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/playerror.imageset/playerror@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/playerror.imageset/playerror@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/playerror.imageset/playerror@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/playerror.imageset/playerror@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/skipbackward.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "skipbackward.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "skipbackward@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "skipbackward@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/skipbackward.imageset/skipbackward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/skipbackward.imageset/skipbackward.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/skipbackward.imageset/skipbackward@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/skipbackward.imageset/skipbackward@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/skipbackward.imageset/skipbackward@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/skipbackward.imageset/skipbackward@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/skipforward.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "skipforward.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "skipforward@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "skipforward@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/skipforward.imageset/skipforward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/skipforward.imageset/skipforward.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/skipforward.imageset/skipforward@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/skipforward.imageset/skipforward@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/skipforward.imageset/skipforward@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/skipforward.imageset/skipforward@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed05x.imageset/0_5x_48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/speed05x.imageset/0_5x_48px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed05x.imageset/0_5x_72px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/speed05x.imageset/0_5x_72px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed05x.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "0_5x_48px.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "0_5x_72px.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed075x.imageset/0_75x_48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/speed075x.imageset/0_75x_48px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed075x.imageset/0_75x_72px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/speed075x.imageset/0_75x_72px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed075x.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "0_75x_48px.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "0_75x_72px.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed125x.imageset/1_25x_48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/speed125x.imageset/1_25x_48px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed125x.imageset/1_25x_72px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/speed125x.imageset/1_25x_72px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed125x.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "1_25x_48px.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "1_25x_72px.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed15x.imageset/1_5x_48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/speed15x.imageset/1_5x_48px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed15x.imageset/1_5x_72px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/speed15x.imageset/1_5x_72px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed15x.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "1_5x_48px.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "1_5x_72px.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed1x.imageset/1x-72px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/speed1x.imageset/1x-72px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed1x.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1x-72px.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed2x.imageset/2x_48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/speed2x.imageset/2x_48px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed2x.imageset/2x_72px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/speed2x.imageset/2x_72px.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/speed2x.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "2x_48px.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "2x_72px.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-clips.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab-clips.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab-clips@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab-clips@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-clips.imageset/tab-clips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-clips.imageset/tab-clips.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-clips.imageset/tab-clips@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-clips.imageset/tab-clips@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-clips.imageset/tab-clips@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-clips.imageset/tab-clips@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-downloads.imageset/tab-downloads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-downloads.imageset/tab-downloads.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-downloads.imageset/tab-downloads@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-downloads.imageset/tab-downloads@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-downloads.imageset/tab-downloads@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-downloads.imageset/tab-downloads@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-find.imageset/tab-find.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-find.imageset/tab-find.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-find.imageset/tab-find@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-find.imageset/tab-find@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-find.imageset/tab-find@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-find.imageset/tab-find@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-more.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab-more.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab-more@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab-more@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-more.imageset/tab-more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-more.imageset/tab-more.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-more.imageset/tab-more@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-more.imageset/tab-more@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-more.imageset/tab-more@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-more.imageset/tab-more@3x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-podcasts.imageset/tab-podcasts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-podcasts.imageset/tab-podcasts.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-podcasts.imageset/tab-podcasts@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-podcasts.imageset/tab-podcasts@2x.png -------------------------------------------------------------------------------- /Podverse/Assets.xcassets/tab-podcasts.imageset/tab-podcasts@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Assets.xcassets/tab-podcasts.imageset/tab-podcasts@3x.png -------------------------------------------------------------------------------- /Podverse/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Podverse/ClipEpisodeTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClipEpisodeTableViewCell.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 9/12/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class ClipEpisodeTableViewCell: UITableViewCell { 13 | 14 | @IBOutlet weak var clipTitle: UILabel! 15 | @IBOutlet weak var time: UILabel! 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Podverse/ClipPodcastTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClipPodcastTableViewCell.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 9/12/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class ClipPodcastTableViewCell: UITableViewCell { 13 | 14 | @IBOutlet weak var clipTitle: UILabel! 15 | @IBOutlet weak var episodePubDate: UILabel! 16 | @IBOutlet weak var episodeTitle: UILabel! 17 | @IBOutlet weak var time: UILabel! 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Podverse/ClipTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClipTableViewCell.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 6/7/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ClipTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var clipTitle: UILabel! 14 | @IBOutlet weak var episodePubDate: UILabel! 15 | @IBOutlet weak var episodeTitle: UILabel! 16 | @IBOutlet weak var podcastImage: UIImageView! 17 | @IBOutlet weak var podcastTitle: UILabel! 18 | @IBOutlet weak var time: UILabel! 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Podverse/Data+ResizeImageData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+ResizeImageData.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/24/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension Data { 13 | func resizeImageData() -> Data? { 14 | guard let image = UIImage(data: self) else { 15 | return nil 16 | } 17 | 18 | var actualHeight: CGFloat = image.size.height 19 | var actualWidth: CGFloat = image.size.width 20 | let maxHeight: CGFloat = 400.0 21 | let maxWidth: CGFloat = 400.0 22 | var imgRatio: CGFloat = actualWidth / actualHeight 23 | let maxRatio: CGFloat = maxWidth / maxHeight 24 | //Half the compression 25 | let compressionQuality: CGFloat = 0.5 26 | if actualHeight > maxHeight || actualWidth > maxWidth { 27 | if imgRatio < maxRatio { 28 | //adjust width according to maxHeight 29 | imgRatio = maxHeight / actualHeight 30 | actualWidth = imgRatio * actualWidth 31 | actualHeight = maxHeight 32 | } 33 | else if imgRatio > maxRatio { 34 | //adjust height according to maxWidth 35 | imgRatio = maxWidth / actualWidth 36 | actualHeight = imgRatio * actualHeight 37 | actualWidth = maxWidth 38 | } 39 | else { 40 | actualHeight = maxHeight 41 | actualWidth = maxWidth 42 | } 43 | } 44 | let rect: CGRect = CGRect(x:0.0, y:0.0, width:actualWidth, height:actualHeight) 45 | UIGraphicsBeginImageContext(rect.size) 46 | 47 | defer { 48 | UIGraphicsEndImageContext() 49 | } 50 | 51 | image.draw(in: rect) 52 | if let img: UIImage = UIGraphicsGetImageFromCurrentImageContext(), let data = UIImageJPEGRepresentation(img, compressionQuality) { 53 | return data 54 | } 55 | else { 56 | return nil 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Podverse/Date+Formatters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+Formatters.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/29/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Date { 12 | func toShortFormatString() -> String { 13 | let formatter = DateFormatter() 14 | formatter.dateStyle = .short 15 | return formatter.string(from: self) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Podverse/DeletingPodcasts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeletingPodcasts.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 11/19/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class DeletingPodcasts { 12 | static let shared = DeletingPodcasts() 13 | var podcastKeys = [String]() 14 | 15 | func addPodcast(podcastId:String?, feedUrl:String?) { 16 | if let podcastId = podcastId { 17 | if self.podcastKeys.filter({$0 == podcastId}).count < 1 { 18 | self.podcastKeys.append(podcastId) 19 | } 20 | } else if let feedUrl = feedUrl { 21 | if self.podcastKeys.filter({$0 == feedUrl}).count < 1 { 22 | self.podcastKeys.append(feedUrl) 23 | } 24 | } 25 | } 26 | 27 | func removePodcast(podcastId:String?, feedUrl:String?) { 28 | if let podcastId = podcastId, let index = self.podcastKeys.index(of: podcastId) { 29 | self.podcastKeys.remove(at: index) 30 | } else if let feedUrl = feedUrl, let index = self.podcastKeys.index(of: feedUrl) { 31 | self.podcastKeys.remove(at: index) 32 | } 33 | } 34 | 35 | func hasMatchingId(podcastId:String) -> Bool { 36 | if let _ = self.podcastKeys.index(of: podcastId) { 37 | return true 38 | } 39 | 40 | return false 41 | } 42 | 43 | func hasMatchingUrl(feedUrl:String) -> Bool { 44 | if let _ = self.podcastKeys.index(of: feedUrl) { 45 | return true 46 | } 47 | 48 | return false 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Podverse/DownloadTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownloadTableViewCell.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 7/4/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DownloadTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var podcastImage: UIImageView! 14 | @IBOutlet weak var episodeTitle: UILabel! 15 | @IBOutlet weak var podcastTitle: UILabel! 16 | @IBOutlet weak var progress: UIProgressView! 17 | @IBOutlet weak var progressStats: UILabel! 18 | @IBOutlet weak var status: UILabel! 19 | 20 | override func awakeFromNib() { 21 | super.awakeFromNib() 22 | // Initialization code 23 | } 24 | 25 | override func setSelected(_ selected: Bool, animated: Bool) { 26 | super.setSelected(selected, animated: animated) 27 | 28 | // Configure the view for the selected state 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Podverse/DownloadingEpisode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownloadingEpisode.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/24/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | final class DownloadingEpisode:Equatable { 13 | var downloadComplete: Bool = false 14 | var mediaUrl: String? 15 | var podcastFeedUrl:String? 16 | var podcastImageUrl: String? 17 | var podcastTitle:String? 18 | var taskIdentifier:Int? 19 | var taskResumeData:Data? 20 | var title:String? 21 | var totalBytesWritten:Float? 22 | var totalBytesExpectedToWrite:Float? 23 | 24 | var formattedTotalBytesDownloaded: String { 25 | get { 26 | if let currentBytes = totalBytesWritten, let totalBytes = totalBytesExpectedToWrite { 27 | // Format the total bytes into a human readable KB or MB number 28 | let dataFormatter = ByteCountFormatter() 29 | 30 | let formattedCurrentBytesDownloaded = dataFormatter.string(fromByteCount: Int64(currentBytes)) 31 | let formattedTotalFileBytes = dataFormatter.string(fromByteCount: Int64(totalBytes)) 32 | 33 | if progress == 1.0 { 34 | return formattedTotalFileBytes 35 | } else { 36 | return "\(formattedCurrentBytesDownloaded) / \(formattedTotalFileBytes)" 37 | } 38 | } else { 39 | return "" 40 | } 41 | } 42 | } 43 | 44 | var progress: Float { 45 | get { 46 | if let currentBytes = totalBytesWritten, let totalBytes = totalBytesExpectedToWrite { 47 | return currentBytes / totalBytes 48 | } else { 49 | return Float(0) 50 | } 51 | } 52 | } 53 | 54 | init(episode:Episode) { 55 | downloadComplete = false 56 | mediaUrl = episode.mediaUrl 57 | podcastFeedUrl = episode.podcast.feedUrl 58 | podcastImageUrl = episode.podcast.imageUrl 59 | podcastTitle = episode.podcast.title 60 | taskIdentifier = nil 61 | taskResumeData = nil 62 | title = episode.title 63 | totalBytesWritten = nil 64 | totalBytesExpectedToWrite = nil 65 | 66 | addToDownloadHistory() 67 | } 68 | 69 | func addToDownloadHistory() { 70 | if var downloadingMediaUrls = UserDefaults.standard.array(forKey: kDownloadingMediaUrls) as? [String], let mediaUrl = self.mediaUrl { 71 | if !downloadingMediaUrls.contains(mediaUrl) { 72 | downloadingMediaUrls.append(mediaUrl) 73 | UserDefaults.standard.setValue(downloadingMediaUrls, forKey: kDownloadingMediaUrls) 74 | } 75 | } else if let mediaUrl = self.mediaUrl { 76 | UserDefaults.standard.setValue([mediaUrl], forKey: kDownloadingMediaUrls) 77 | } 78 | 79 | } 80 | 81 | func removeFromDownloadHistory() { 82 | if let downloadingMediaUrls = UserDefaults.standard.array(forKey: kDownloadingMediaUrls) as? [String] { 83 | let results = downloadingMediaUrls.filter { $0 != mediaUrl } 84 | UserDefaults.standard.setValue(results, forKey: kDownloadingMediaUrls) 85 | } 86 | } 87 | 88 | } 89 | 90 | func == (lhs: DownloadingEpisode, rhs: DownloadingEpisode) -> Bool { 91 | return lhs.mediaUrl == rhs.mediaUrl 92 | } 93 | -------------------------------------------------------------------------------- /Podverse/DownloadingEpisodeList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownloadingEpisodeList.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/24/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class DownloadingEpisodeList { 12 | static var shared = DownloadingEpisodeList() 13 | 14 | var downloadingEpisodes = [DownloadingEpisode]() 15 | 16 | static func removeDownloadingEpisodeWithMediaURL(mediaUrl:String?) { 17 | var downloadingEpisodes = DownloadingEpisodeList.shared.downloadingEpisodes 18 | 19 | if let mediaUrl = mediaUrl, let index = downloadingEpisodes.index(where: { $0.mediaUrl == mediaUrl }), index < downloadingEpisodes.count { 20 | downloadingEpisodes[index].removeFromDownloadHistory() 21 | downloadingEpisodes.remove(at: index) 22 | PVDownloader.shared.decrementBadge() 23 | DownloadingEpisodeList.shared.downloadingEpisodes = downloadingEpisodes 24 | } 25 | } 26 | 27 | static func removeAllEpisodesForPodcast(feedUrl: String) { 28 | DownloadingEpisodeList.shared.downloadingEpisodes = DownloadingEpisodeList.shared.downloadingEpisodes.filter({$0.podcastFeedUrl != feedUrl}) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Podverse/Episode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Episode.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/24/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | class Episode: NSManagedObject { 13 | @NSManaged var duration: NSNumber? 14 | @NSManaged var fileName: String? 15 | @NSManaged var guid: String? 16 | @NSManaged var link: String? 17 | @NSManaged var mediaBytes: NSNumber? 18 | @NSManaged var mediaType: String? 19 | @NSManaged var mediaUrl: String? 20 | @NSManaged var pubDate: Date? 21 | @NSManaged var summary: String? 22 | @NSManaged var title: String? 23 | @NSManaged var uuid: String? 24 | @NSManaged var podcast: Podcast 25 | 26 | static func episodeForMediaUrl(mediaUrlString: String, managedObjectContext:NSManagedObjectContext? = nil) -> Episode? { 27 | let moc = managedObjectContext ?? CoreDataHelper.createMOCForThread(threadType: .mainThread) 28 | 29 | let predicate = NSPredicate(format: "mediaUrl == %@", mediaUrlString) 30 | let episodeSet = CoreDataHelper.fetchEntities(className: "Episode", predicate: predicate, moc:moc) as? [Episode] 31 | 32 | return episodeSet?.first 33 | } 34 | 35 | static func jsonToPlayerHistoryItem(json: [String:Any]) -> PlayerHistoryItem? { 36 | 37 | if let podcast = json["podcast"] as? [String:Any], let isPublic = json["isPublic"] as? Bool { 38 | let podcastId = podcast["id"] as? String 39 | let podcastTitle = podcast["title"] as? String 40 | let podcastImageUrl = podcast["imageUrl"] as? String 41 | 42 | let episodeId = json["id"] as? String 43 | let episodeDuration = json["duration"] as? Int64 44 | let episodeMediaUrl = json["mediaUrl"] as? String 45 | let episodeTitle = json["title"] as? String 46 | let episodeImageUrl = json["imageUrl"] as? String 47 | let episodeSummary = json["summary"] as? String 48 | let episodePubDate = (json["pubDate"] as? String)?.toServerDate() 49 | let episodeLastUpdated = (json["lastUpdated"] as? String)?.toServerDate() 50 | 51 | let item = PlayerHistoryItem(mediaRefId: nil, podcastId: podcastId, podcastFeedUrl: nil, podcastTitle: podcastTitle, podcastImageUrl: podcastImageUrl, episodeDuration: episodeDuration, episodeId: episodeId, episodeMediaUrl: episodeMediaUrl, episodeTitle: episodeTitle, episodeImageUrl: episodeImageUrl, episodeSummary: episodeSummary, episodePubDate: episodePubDate, startTime: nil, endTime: nil, clipTitle: nil, ownerName: nil, ownerId: nil, hasReachedEnd: false, lastPlaybackPosition: nil, lastUpdated: episodeLastUpdated, isPublic: isPublic) 52 | 53 | return item 54 | } 55 | 56 | return nil 57 | } 58 | 59 | static func retrieveEpisodeFromServer(id:String, completion: @escaping (_ playerHistoryItem:PlayerHistoryItem?) -> Void) { 60 | if let url = URL(string: BASE_URL + "api/episodes") { 61 | 62 | let request = NSMutableURLRequest(url: url, cachePolicy: NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30) 63 | 64 | request.setValue("application/json", forHTTPHeaderField: "Content-Type") 65 | 66 | request.httpMethod = "POST" 67 | 68 | var values: [String: Any] = [:] 69 | values["id"] = id 70 | 71 | do { 72 | request.httpBody = try JSONSerialization.data(withJSONObject: values, options: []) 73 | } catch { 74 | print("Error: \(error.localizedDescription)") 75 | } 76 | 77 | let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in 78 | 79 | hideNetworkActivityIndicator() 80 | guard error == nil else { 81 | print("Error: \(error?.localizedDescription ?? "Unknown Error")") 82 | DispatchQueue.main.async { 83 | completion(nil) 84 | } 85 | return 86 | } 87 | 88 | if let data = data { 89 | do { 90 | if let responseJSON = try JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] { 91 | let item = jsonToPlayerHistoryItem(json: responseJSON) 92 | DispatchQueue.main.async { 93 | completion(item) 94 | } 95 | } 96 | } catch { 97 | print("Error: " + error.localizedDescription) 98 | } 99 | } 100 | } 101 | 102 | task.resume() 103 | 104 | } 105 | } 106 | 107 | // This is a hacky method for determining what the Podverse episode ID is for an episode that the user has parsed on their phone locally. 108 | // In the future, we could avoid this by parsing all feeds on our server instead of on users' devices. 109 | static func retrieveEpisodeIdFromServer(mediaUrl:String, completion: @escaping (_ episodeId:String?) -> Void) { 110 | if let url = URL(string: BASE_URL + "api/episodes/id") { 111 | 112 | let request = NSMutableURLRequest(url: url, cachePolicy: NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30) 113 | 114 | request.setValue("application/json", forHTTPHeaderField: "Content-Type") 115 | request.httpMethod = "POST" 116 | 117 | var values: [String:Any] = [:] 118 | values["mediaUrl"] = mediaUrl 119 | 120 | do { 121 | request.httpBody = try JSONSerialization.data(withJSONObject: values, options: []) 122 | } catch { 123 | print(error) 124 | DispatchQueue.main.async { 125 | completion(nil) 126 | } 127 | return 128 | } 129 | 130 | let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in 131 | 132 | hideNetworkActivityIndicator() 133 | guard error == nil else { 134 | print("Error: \(error?.localizedDescription ?? "Unknown Error")") 135 | DispatchQueue.main.async { 136 | completion(nil) 137 | } 138 | return 139 | } 140 | 141 | if let data = data { 142 | do { 143 | if let responseJSON = try JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] { 144 | if let id = responseJSON["id"] as? String { 145 | DispatchQueue.main.async { 146 | completion(id) 147 | } 148 | } else { 149 | DispatchQueue.main.async { 150 | completion(nil) 151 | } 152 | } 153 | } 154 | } catch { 155 | print("Error: " + error.localizedDescription) 156 | } 157 | } 158 | } 159 | 160 | task.resume() 161 | 162 | } 163 | } 164 | 165 | static let episodeKey = "episode" 166 | 167 | } 168 | 169 | 170 | -------------------------------------------------------------------------------- /Podverse/EpisodeTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EpisodeTableViewCell.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 5/6/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class EpisodeTableViewCell: UITableViewCell { 12 | @IBOutlet weak var title: UILabel! 13 | @IBOutlet weak var summary: UILabel! 14 | @IBOutlet weak var duration: UILabel! 15 | @IBOutlet weak var pubDate: UILabel! 16 | @IBOutlet weak var button: UIButton! 17 | @IBOutlet weak var activityIndicator: UIActivityIndicatorView! 18 | 19 | override func awakeFromNib() { 20 | // The activityIndicator can be nil in the SearchPodcastViewController 21 | if self.activityIndicator != nil { 22 | self.activityIndicator.hidesWhenStopped = true 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Podverse/FindBrowseGroupsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindBrowseGroupsViewController.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 10/22/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | //import UIKit 10 | // 11 | //class FindBrowseGroupsViewController: PVViewController { 12 | // 13 | // var categories = [SearchCategory]() 14 | // var networks = [SearchNetwork]() 15 | // 16 | // var categoryParentId:Int64? 17 | // var shouldLoadCategories:Bool = false 18 | // var shouldLoadNetworks:Bool = false 19 | // 20 | // @IBOutlet weak var tableView: UITableView! 21 | // 22 | // override func viewDidLoad() { 23 | // super.viewDidLoad() 24 | // 25 | // if self.shouldLoadCategories { 26 | // 27 | // self.title = "Categories"au 28 | // 29 | // SearchCategory.retrieveCategoriesFromServer(parentId: nil, { categoriesArray in 30 | // DispatchQueue.main.async { 31 | // if let categoriesArray = categoriesArray { 32 | // let filteredArray = SearchCategory.filterCategories(categories: categoriesArray, parentId: self.categoryParentId) 33 | // self.categories = filteredArray 34 | // self.tableView.reloadData() 35 | // } 36 | // } 37 | // }) 38 | // } else if self.shouldLoadNetworks { 39 | // 40 | // self.title = "Networks" 41 | // 42 | // SearchNetwork.retrieveNetworksFromServer({ networksArray in 43 | // DispatchQueue.main.async { 44 | // if let networksArray = networksArray { 45 | // self.networks = networksArray 46 | // self.tableView.reloadData() 47 | // } 48 | // } 49 | // }) 50 | // } 51 | // 52 | // } 53 | // 54 | //} 55 | // 56 | //extension FindBrowseGroupsViewController:UITableViewDelegate, UITableViewDataSource { 57 | // 58 | // func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 59 | // if self.shouldLoadCategories { 60 | // return self.categories.count 61 | // } else { 62 | // return self.networks.count 63 | // } 64 | // } 65 | // 66 | // func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 67 | // let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as UITableViewCell 68 | // 69 | // if self.shouldLoadCategories { 70 | // let title = self.categories[indexPath.row].name 71 | // cell.textLabel?.text = title 72 | // } else { 73 | // let title = self.networks[indexPath.row].name 74 | // cell.textLabel?.text = title 75 | // } 76 | // 77 | // return cell 78 | // } 79 | // 80 | // func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 81 | // let sender = self.shouldLoadCategories ? "Categories" : "Networks" 82 | // self.performSegue(withIdentifier: "Show Browse Podcasts", sender: sender) 83 | // } 84 | // 85 | // override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 86 | // if segue.identifier == "Show Browse Podcasts", let findBrowsePodcastsVC = segue.destination as? FindBrowsePodcastsViewController, let indexPath = self.tableView.indexPathForSelectedRow { 87 | // 88 | // if let sender = sender as? String, sender == "Categories" { 89 | // if indexPath.row < self.categories.count { 90 | // let category = self.categories[indexPath.row] 91 | // findBrowsePodcastsVC.groupTitle = category.name 92 | // findBrowsePodcastsVC.categoryName = category.name 93 | // } 94 | // } else { 95 | // if indexPath.row < self.networks.count { 96 | // let network = self.networks[indexPath.row] 97 | // if let name = network.name { 98 | // findBrowsePodcastsVC.groupTitle = name 99 | // findBrowsePodcastsVC.networkName = name 100 | // } 101 | // } 102 | // } 103 | // 104 | // } 105 | // } 106 | // 107 | //} 108 | 109 | -------------------------------------------------------------------------------- /Podverse/FindBrowsePodcastsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindBrowsePodcastsViewController.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 10/22/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class FindBrowsePodcastsViewController: PVViewController { 12 | 13 | var podcasts = [SearchPodcast]() 14 | var groupTitle = "" 15 | var categoryName:String? 16 | var networkName:String? 17 | 18 | @IBOutlet weak var tableView: UITableView! 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | var params = Dictionary() 24 | 25 | if let categoryName = categoryName { 26 | self.title = "Category" 27 | params["filters[categories.name]"] = categoryName 28 | } else if let networkName = networkName { 29 | self.title = "Network" 30 | params["filters[network.name]"] = networkName 31 | } 32 | 33 | params["sort_by"] = "buzz_score" 34 | params["sort_order"] = "desc" 35 | 36 | // SearchClientSwift.search(query: "*", params: params, type: "shows") { (serviceResponse) in 37 | // 38 | // self.podcasts.removeAll() 39 | // 40 | // if let response = serviceResponse.0 { 41 | // // let page = response["page"] as? String 42 | // // let query = response["query"] as? String 43 | // // let results_per_page = response["results_per_page"] as? String 44 | // // let total_results = response["total_results"] as? String 45 | // 46 | // if let results = response["results"] as? [AnyObject] { 47 | // for result in results { 48 | // if let searchResult = SearchPodcast.convertJSONToSearchPodcast(result) { 49 | // self.podcasts.append(searchResult) 50 | // } 51 | // } 52 | // } 53 | // 54 | // DispatchQueue.main.async { 55 | // self.tableView.reloadData() 56 | // } 57 | // } 58 | // 59 | // if let error = serviceResponse.1 { 60 | // print(error.localizedDescription) 61 | // } 62 | // 63 | // } 64 | 65 | } 66 | 67 | } 68 | 69 | extension FindBrowsePodcastsViewController: UITableViewDataSource, UITableViewDelegate { 70 | 71 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 72 | return 44 73 | } 74 | 75 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 76 | return self.groupTitle 77 | } 78 | 79 | func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { 80 | return 96.5 81 | } 82 | 83 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 84 | return 96.5 85 | } 86 | 87 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 88 | return self.podcasts.count 89 | } 90 | 91 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 92 | let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! PodcastSearchResultTableViewCell 93 | 94 | let podcast = self.podcasts[indexPath.row] 95 | 96 | cell.title.text = podcast.title 97 | cell.hosts.text = podcast.hosts 98 | cell.categories.text = podcast.categories 99 | 100 | cell.pvImage.image = Podcast.retrievePodcastImage(podcastImageURLString: podcast.imageUrl, feedURLString: nil, completion: { image in 101 | cell.pvImage.image = image 102 | }) 103 | 104 | return cell 105 | } 106 | 107 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 108 | let podcast = self.podcasts[indexPath.row] 109 | SearchPodcast.showSearchPodcastActions(searchPodcast: podcast, vc: self) 110 | } 111 | 112 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 113 | 114 | if segue.identifier == "Show Search Podcast" { 115 | if let searchPodcastVC = segue.destination as? SearchPodcastViewController, let indexPath = self.tableView.indexPathForSelectedRow, indexPath.row < self.podcasts.count { 116 | let podcast = podcasts[indexPath.row] 117 | searchPodcastVC.searchPodcast = podcast 118 | searchPodcastVC.filterTypeOverride = .about 119 | } 120 | } 121 | 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /Podverse/FindSearchTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindSearchTableViewController.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 7/8/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class FindSearchTableViewController: PVViewController { 12 | 13 | var podcasts = [SearchPodcast]() 14 | 15 | @IBOutlet weak var activityView: UIView! 16 | @IBOutlet weak var activityIndicator: UIActivityIndicatorView! 17 | @IBOutlet weak var tableView: UITableView! 18 | @IBOutlet weak var searchBar: UISearchBar! 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | self.title = "Search" 24 | 25 | self.searchBar.delegate = self 26 | self.searchBar.returnKeyType = .done 27 | 28 | self.activityIndicator.hidesWhenStopped = true 29 | hideActivityIndicator() 30 | 31 | self.tableView.isHidden = true 32 | 33 | let requestPodcast = UIBarButtonItem(title: "Request", style: .plain, target: self, action: #selector(segueToRequestPodcastForm)) 34 | self.navigationItem.rightBarButtonItems = [requestPodcast] 35 | 36 | loadSearchForPodcastsMessage() 37 | } 38 | 39 | @objc func segueToRequestPodcastForm() { 40 | self.tableView.reloadData() 41 | if let webKitVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "WebKitVC") as? WebKitViewController { 42 | webKitVC.urlString = kFormRequestPodcastUrl 43 | self.hideNowPlayingBar() 44 | self.navigationController?.pushViewController(webKitVC, animated: true) 45 | } 46 | } 47 | 48 | func loadNoDataView(message: String, buttonTitle: String?, buttonPressed: Selector?) { 49 | 50 | if let noDataView = self.view.subviews.first(where: { $0.tag == kNoDataViewTag}) { 51 | noDataView.removeFromSuperview() 52 | } 53 | 54 | self.addNoDataViewWithMessage(message, buttonTitle: buttonTitle, buttonImage: nil, retryPressed: buttonPressed) 55 | 56 | showNoDataView() 57 | 58 | } 59 | 60 | func loadNoInternetMessage() { 61 | loadNoDataView(message: Strings.Errors.noClipsInternet, buttonTitle: "Retry", buttonPressed: #selector(ClipsTableViewController.resetAndRetrieveClips)) 62 | } 63 | 64 | func loadNoResultsMessage() { 65 | loadNoDataView(message: Strings.Errors.noSearchResultsFound, buttonTitle: "Request a podcast", buttonPressed: #selector(segueToRequestPodcastForm)) 66 | } 67 | 68 | func loadSearchForPodcastsMessage() { 69 | loadNoDataView(message: "Search for podcasts by title", buttonTitle: nil, buttonPressed: nil) 70 | } 71 | 72 | func showActivityIndicator() { 73 | self.tableView.isHidden = true 74 | self.activityIndicator.startAnimating() 75 | self.activityView.isHidden = false 76 | } 77 | 78 | func hideActivityIndicator() { 79 | self.activityIndicator.stopAnimating() 80 | self.activityView.isHidden = true 81 | } 82 | 83 | @objc fileprivate func searchPodcasts(_ text:String) { 84 | self.podcasts.removeAll() 85 | 86 | guard checkForConnectivity() else { 87 | loadNoInternetMessage() 88 | return 89 | } 90 | 91 | showActivityIndicator() 92 | 93 | SearchPodcast.searchPodcastsByTitle(title: text) { searchPodcasts in 94 | if let searchPodcasts = searchPodcasts { 95 | self.podcasts = searchPodcasts 96 | } 97 | 98 | DispatchQueue.main.async { 99 | self.hideActivityIndicator() 100 | 101 | if self.podcasts.isEmpty { 102 | self.loadNoResultsMessage() 103 | } else { 104 | self.tableView.reloadData() 105 | self.tableView.isHidden = false 106 | } 107 | 108 | } 109 | } 110 | } 111 | 112 | } 113 | 114 | extension FindSearchTableViewController: UITableViewDataSource, UITableViewDelegate { 115 | 116 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 117 | return 96.5 118 | } 119 | 120 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 121 | return self.podcasts.count 122 | } 123 | 124 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 125 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! PodcastSearchResultTableViewCell 126 | 127 | let podcast = self.podcasts[indexPath.row] 128 | 129 | cell.title.text = podcast.title 130 | cell.hosts.text = podcast.hosts 131 | cell.categories.text = podcast.categories 132 | 133 | cell.pvImage.sd_setImage(with: URL(string: podcast.imageUrl ?? ""), placeholderImage: #imageLiteral(resourceName: "PodverseIcon")) 134 | 135 | return cell 136 | } 137 | 138 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 139 | let podcast = self.podcasts[indexPath.row] 140 | SearchPodcast.showSearchPodcastActions(searchPodcast: podcast, vc: self) 141 | } 142 | 143 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 144 | if segue.identifier == "Show Search Podcast" { 145 | if let searchPodcastVC = segue.destination as? SearchPodcastViewController, let indexPath = self.tableView.indexPathForSelectedRow, indexPath.row < self.podcasts.count { 146 | let podcast = podcasts[indexPath.row] 147 | searchPodcastVC.searchPodcast = podcast 148 | 149 | if let sender = sender as? String, sender == "About" { 150 | searchPodcastVC.filterTypeOverride = .about 151 | } else if let sender = sender as? String, sender == "Clips" { 152 | searchPodcastVC.filterTypeOverride = .clips 153 | } else if let sender = sender as? String, sender == "Episodes" { 154 | searchPodcastVC.filterTypeOverride = .episodes 155 | } 156 | } 157 | } 158 | } 159 | 160 | } 161 | 162 | extension FindSearchTableViewController: UISearchBarDelegate { 163 | func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { 164 | searchBar.resignFirstResponder() 165 | } 166 | 167 | func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { 168 | if let text = searchBar.text, text.count > 2 { 169 | NSObject.cancelPreviousPerformRequests(withTarget: self) 170 | perform(#selector(searchPodcasts(_:)), with: text, afterDelay: 0.4) 171 | } 172 | } 173 | 174 | func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { 175 | searchBar.resignFirstResponder() 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Podverse/FindTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindTableViewController.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/15/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class FindTableViewController: PVViewController { 12 | 13 | @IBOutlet weak var tableView: UITableView! 14 | 15 | let reachability = PVReachability.shared 16 | 17 | let findSearchArray = ["Search", "Add Podcast by RSS"] 18 | 19 | var podcastVC:PodcastsTableViewController? { 20 | get { 21 | if let navController = self.tabBarController?.viewControllers?.first as? UINavigationController, let podcastTable = navController.topViewController as? PodcastsTableViewController { 22 | return podcastTable 23 | } 24 | 25 | return nil 26 | } 27 | 28 | } 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | self.title = "Find" 34 | } 35 | 36 | } 37 | 38 | extension FindTableViewController:UITableViewDelegate, UITableViewDataSource { 39 | func numberOfSections(in tableView: UITableView) -> Int { 40 | return 1 41 | } 42 | 43 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 44 | return 44 45 | } 46 | 47 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 48 | return "Podcasts" 49 | } 50 | 51 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 52 | return findSearchArray.count 53 | } 54 | 55 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 56 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell 57 | let title = findSearchArray[indexPath.row] 58 | cell.textLabel!.text = title 59 | return cell 60 | } 61 | 62 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 63 | if indexPath.row == 0 { 64 | self.performSegue(withIdentifier: "Search for Podcasts", sender: tableView) 65 | } 66 | else { 67 | if !checkForConnectivity() { 68 | showInternetNeededAlertWithDescription(message: "Connect to WiFi or cellular data to add podcast by RSS URL.") 69 | return 70 | } 71 | let addByRSSAlert = UIAlertController(title: "Add Podcast by RSS Feed", message: "Type the RSS feed URL below. NOTE: Creating clips is not supported for podcast's added by RSS Feed.", preferredStyle: UIAlertControllerStyle.alert) 72 | 73 | addByRSSAlert.addTextField(configurationHandler: {(textField: UITextField!) in 74 | textField.placeholder = "https://rssfeed.example.com/" 75 | }) 76 | 77 | addByRSSAlert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil)) 78 | 79 | addByRSSAlert.addAction(UIAlertAction(title: "Add", style: .default, handler: { (action: UIAlertAction!) in 80 | if let textField = addByRSSAlert.textFields?[0], let text = textField.text { 81 | DispatchQueue.global().async { 82 | PVSubscriber.subscribeToPodcast(podcastId: nil, feedUrl: text) 83 | } 84 | } 85 | })) 86 | 87 | present(addByRSSAlert, animated: true, completion: nil) 88 | 89 | } 90 | 91 | tableView.deselectRow(at: indexPath, animated: true) 92 | 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /Podverse/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.1.0 21 | CFBundleURLTypes 22 | 23 | 24 | CFBundleTypeRole 25 | Editor 26 | CFBundleURLSchemes 27 | 28 | podverse 29 | 30 | 31 | 32 | CFBundleVersion 33 | 8 34 | Fabric 35 | 36 | APIKey 37 | 4de8d263bbd958f598aae11ba2d716f6c29190e9 38 | Kits 39 | 40 | 41 | KitInfo 42 | 43 | KitName 44 | Crashlytics 45 | 46 | 47 | 48 | LSRequiresIPhoneOS 49 | 50 | NSAppTransportSecurity 51 | 52 | NSAllowsArbitraryLoads 53 | 54 | 55 | UIBackgroundModes 56 | 57 | audio 58 | 59 | UIMainStoryboardFile 60 | Main 61 | UIRequiredDeviceCapabilities 62 | 63 | armv7 64 | 65 | UIStatusBarStyle 66 | UIStatusBarStyleLightContent 67 | UIStatusBarTintParameters 68 | 69 | UINavigationBar 70 | 71 | Style 72 | UIBarStyleDefault 73 | Translucent 74 | 75 | 76 | 77 | UISupportedInterfaceOrientations 78 | 79 | UIInterfaceOrientationPortrait 80 | 81 | UIViewControllerBasedStatusBarAppearance 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /Podverse/Int64+MediaPlayerString.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int64+MediaPlayerString.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 5/29/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | extension Int64 { 10 | func toMediaPlayerString() -> String { 11 | let hours = self / 3600 12 | let minutes = self / 60 % 60 13 | let seconds = self % 60 14 | 15 | var timeString = String(format:"%02i:%02i:%02i", hours, minutes, seconds) 16 | 17 | if hours < 10 { 18 | timeString = String(format:"%01i:%02i:%02i", hours, minutes, seconds) 19 | } 20 | 21 | if hours == 0 { 22 | if minutes > 9 { 23 | timeString = String(format:"%02i:%02i", minutes, seconds) 24 | } else { 25 | timeString = String(format:"%01i:%02i", minutes, seconds) 26 | } 27 | } 28 | 29 | return timeString 30 | } 31 | 32 | func toDurationString() -> String { 33 | let hours = self / 3600 34 | let minutes = self / 60 % 60 35 | let seconds = self % 60 36 | 37 | var timeString = "" 38 | 39 | if hours > 0 { 40 | timeString += String(hours) + "h " 41 | } 42 | 43 | if minutes > 0 { 44 | timeString += String(minutes) + "m " 45 | } 46 | 47 | if seconds > 0 { 48 | timeString += String(seconds) + "s" 49 | } 50 | 51 | return timeString.trimmingCharacters(in: .whitespaces) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Podverse/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewController.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/29/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Lock 11 | 12 | class LoginViewController: PVViewController { 13 | 14 | let pvAuth = PVAuth.shared 15 | 16 | @IBOutlet weak var loginToLabel: UILabel! 17 | @IBOutlet weak var loginToText: UITextView! 18 | @IBOutlet weak var noLoginNeededToLabel: UILabel! 19 | @IBOutlet weak var noLoginNeededToText: UITextView! 20 | @IBOutlet weak var loginButton: UIButton! 21 | @IBOutlet weak var noThanksButton: UIButton! 22 | @IBOutlet weak var activityIndicator: UIActivityIndicatorView! 23 | @IBOutlet weak var loggingInLabel: UILabel! 24 | 25 | @IBAction func login(_ sender: Any) { 26 | pvAuth.showAuth0Lock(vc: self) 27 | } 28 | 29 | @IBAction func cancel(_ sender: Any) { 30 | dismiss(animated: true, completion: nil) 31 | } 32 | 33 | fileprivate func addObservers() { 34 | NotificationCenter.default.addObserver(self, selector: #selector(self.loggingIn(_:)), name: .loggingIn, object: nil) 35 | NotificationCenter.default.addObserver(self, selector: #selector(self.loggedInSuccessfully(_:)), name: .loggedInSuccessfully, object: nil) 36 | NotificationCenter.default.addObserver(self, selector: #selector(self.loginFailed(_:)), name: .loginFailed, object: nil) 37 | } 38 | 39 | fileprivate func removeObservers() { 40 | NotificationCenter.default.removeObserver(self, name: .loggingIn, object: nil) 41 | NotificationCenter.default.removeObserver(self, name: .loggedInSuccessfully, object: nil) 42 | NotificationCenter.default.removeObserver(self, name: .loginFailed, object: nil) 43 | } 44 | 45 | override func viewDidLoad() { 46 | super.viewDidLoad() 47 | addObservers() 48 | self.activityIndicator.hidesWhenStopped = true 49 | self.loggingInLabel.isHidden = true 50 | } 51 | 52 | deinit { 53 | removeObservers() 54 | } 55 | 56 | } 57 | 58 | extension LoginViewController { 59 | @objc func loggingIn(_ notification:Notification) { 60 | self.loginToLabel.isHidden = true 61 | self.loginToText.isHidden = true 62 | self.noLoginNeededToLabel.isHidden = true 63 | self.noLoginNeededToText.isHidden = true 64 | self.loginButton.isHidden = true 65 | self.noThanksButton.isHidden = true 66 | self.loggingInLabel.isHidden = false 67 | self.activityIndicator.startAnimating() 68 | } 69 | 70 | @objc func loggedInSuccessfully(_ notification:Notification) { 71 | self.activityIndicator.stopAnimating() 72 | self.dismiss(animated: true, completion: nil) 73 | } 74 | 75 | @objc func loginFailed(_ notification:Notification) { 76 | self.loginToLabel.isHidden = false 77 | self.loginToText.isHidden = false 78 | self.noLoginNeededToLabel.isHidden = false 79 | self.noLoginNeededToText.isHidden = false 80 | self.loginButton.isHidden = false 81 | self.noThanksButton.isHidden = false 82 | self.loggingInLabel.isHidden = true 83 | self.activityIndicator.stopAnimating() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Podverse/MoreTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoreTableViewController.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/15/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MoreTableViewController: PVViewController { 12 | 13 | let pvAuth = PVAuth.shared 14 | 15 | @IBOutlet weak var tableView: UITableView! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | self.title = "More" 21 | 22 | addObservers() 23 | } 24 | 25 | deinit { 26 | removeObservers() 27 | } 28 | 29 | fileprivate func addObservers() { 30 | NotificationCenter.default.addObserver(self, selector: #selector(self.loggingIn(_:)), name: .loggingIn, object: nil) 31 | NotificationCenter.default.addObserver(self, selector: #selector(self.loggedInSuccessfully(_:)), name: .loggedInSuccessfully, object: nil) 32 | NotificationCenter.default.addObserver(self, selector: #selector(self.loginFailed(_:)), name: .loginFailed, object: nil) 33 | NotificationCenter.default.addObserver(self, selector: #selector(self.loggedOutSuccessfully(_:)), name: .loggedOutSuccessfully, object: nil) 34 | } 35 | 36 | fileprivate func removeObservers() { 37 | NotificationCenter.default.removeObserver(self, name: .loggingIn, object: nil) 38 | NotificationCenter.default.removeObserver(self, name: .loggedInSuccessfully, object: nil) 39 | NotificationCenter.default.removeObserver(self, name: .loginFailed, object: nil) 40 | NotificationCenter.default.removeObserver(self, name: .loggedOutSuccessfully, object: nil) 41 | } 42 | 43 | } 44 | 45 | extension MoreTableViewController:UITableViewDelegate, UITableViewDataSource { 46 | 47 | func numberOfSections(in tableView: UITableView) -> Int { 48 | return 2 49 | } 50 | 51 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 52 | if section == 0 { 53 | return "Features" 54 | } else { 55 | return "Podverse" 56 | } 57 | } 58 | 59 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 60 | if section == 0 { 61 | return 3 62 | } else { 63 | return 2 64 | } 65 | } 66 | 67 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 68 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 69 | let section = indexPath.section 70 | let row = indexPath.row 71 | 72 | if section == 0 { 73 | if row == 0 { 74 | cell.textLabel?.text = "Playlists" 75 | } else if row == 1 { 76 | cell.textLabel?.text = "Settings" 77 | } else if row == 2 { 78 | if let _ = UserDefaults.standard.string(forKey: "idToken") { 79 | cell.textLabel?.text = "Log out" 80 | } else { 81 | cell.textLabel?.text = "Log in" 82 | } 83 | } 84 | } else { 85 | if row == 0 { 86 | cell.textLabel?.text = "Feedback" 87 | } else { 88 | cell.textLabel?.text = "About" 89 | } 90 | } 91 | 92 | return cell 93 | } 94 | 95 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 96 | 97 | let section = indexPath.section 98 | let row = indexPath.row 99 | 100 | if section == 0 { 101 | if row == 0 { 102 | performSegue(withIdentifier: "Show Playlists", sender: nil) 103 | } else if row == 1 { 104 | performSegue(withIdentifier: "Show Settings", sender: nil) 105 | } else if row == 2 { 106 | if let _ = UserDefaults.standard.string(forKey: "idToken") { 107 | 108 | let logoutAlert = UIAlertController(title: "Log out", message: "Are you sure?", preferredStyle: .alert) 109 | 110 | logoutAlert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { (action: UIAlertAction!) in 111 | self.pvAuth.removeUserInfo() 112 | tableView.reloadData() 113 | })) 114 | 115 | logoutAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 116 | 117 | present(logoutAlert, animated: true, completion: nil) 118 | 119 | } else { 120 | pvAuth.showAuth0Lock(vc: self) 121 | } 122 | } 123 | } else { 124 | if row == 0 { 125 | if let webKitVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "WebKitVC") as? WebKitViewController { 126 | webKitVC.urlString = kFormContactUrl 127 | self.navigationController?.pushViewController(webKitVC, animated: true) 128 | self.tabBarController?.hidePlayerView() 129 | } 130 | } else { 131 | if let url = URL(string: BASE_URL + "about"), let webVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "AboutVC") as? AboutViewController { 132 | webVC.requestUrl = url 133 | self.navigationController?.pushViewController(webVC, animated: true) 134 | self.tabBarController?.hidePlayerView() 135 | } 136 | } 137 | } 138 | 139 | tableView.deselectRow(at: indexPath, animated: true) 140 | } 141 | 142 | } 143 | 144 | extension MoreTableViewController { 145 | 146 | @objc func loggingIn(_ notification:Notification) { 147 | let indexPath = IndexPath(row: 1, section: 0) 148 | guard let cell = self.tableView.cellForRow(at: indexPath) else { 149 | return 150 | } 151 | 152 | cell.textLabel?.text = nil 153 | let activityIndicator = UIActivityIndicatorView() 154 | activityIndicator.color = UIColor.black 155 | cell.addSubview(activityIndicator) 156 | activityIndicator.startAnimating() 157 | } 158 | 159 | @objc func loggedInSuccessfully(_ notification:Notification) { 160 | self.tableView.reloadData() 161 | } 162 | 163 | @objc func loginFailed(_ notification:Notification) { 164 | let indexPath = IndexPath(row: 1, section: 0) 165 | guard let cell = self.tableView.cellForRow(at: indexPath) else { 166 | return 167 | } 168 | 169 | for view in cell.subviews { 170 | if let activityIndicator = view as? UIActivityIndicatorView { 171 | activityIndicator.removeFromSuperview() 172 | } 173 | } 174 | cell.textLabel?.text = "Login" 175 | } 176 | 177 | @objc func loggedOutSuccessfully(_ notification:Notification) { 178 | self.tableView.reloadData() 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /Podverse/NowPlayingBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NowPlayingBar.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 7/15/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import StreamingKit 11 | 12 | protocol NowPlayingBarDelegate:class { 13 | func didTapView() 14 | } 15 | 16 | class NowPlayingBar:UIView { 17 | @IBOutlet weak var podcastImageView: UIImageView! 18 | @IBOutlet weak var podcastTitleLabel: UILabel! 19 | @IBOutlet weak var playButton: UIButton! 20 | @IBOutlet weak var episodeTitle: UILabel! 21 | @IBOutlet weak var activityIndicator: UIActivityIndicatorView! 22 | 23 | weak var delegate:NowPlayingBarDelegate? 24 | 25 | let pvMediaPlayer = PVMediaPlayer.shared 26 | 27 | @IBAction func didTapView(_ sender: Any) { 28 | self.delegate?.didTapView() 29 | } 30 | 31 | @IBAction func playPause(_ sender: Any) { 32 | PVMediaPlayer.shared.playOrPause() 33 | } 34 | 35 | override init(frame: CGRect) { 36 | super.init(frame: frame) 37 | setupView() 38 | self.activityIndicator.hidesWhenStopped = true 39 | } 40 | 41 | required init?(coder aDecoder: NSCoder) { 42 | super.init(coder: aDecoder) 43 | setupView() 44 | self.activityIndicator.hidesWhenStopped = true 45 | } 46 | 47 | private func setupView() { 48 | let view = viewFromNibForClass() 49 | view.frame = bounds 50 | 51 | view.autoresizingMask = [ 52 | UIViewAutoresizing.flexibleWidth, 53 | UIViewAutoresizing.flexibleHeight 54 | ] 55 | 56 | addSubview(view) 57 | } 58 | 59 | private func viewFromNibForClass() -> UIView { 60 | let bundle = Bundle(for: type(of: self)) 61 | let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle) 62 | let view = nib.instantiate(withOwner: self, options: nil).first as! UIView 63 | 64 | return view 65 | } 66 | 67 | static var playerHeight:CGFloat { 68 | return 60.0 69 | } 70 | 71 | func togglePlayIcon() { 72 | let audioPlayer = PVMediaPlayer.shared.audioPlayer 73 | DispatchQueue.main.async { 74 | if audioPlayer.state == .stopped || audioPlayer.state == .paused { 75 | self.activityIndicator.isHidden = true 76 | self.playButton.setImage(UIImage(named:"play"), for: .normal) 77 | self.playButton.tintColor = UIColor.black 78 | self.playButton.isHidden = false 79 | } else if audioPlayer.state == .error { 80 | self.activityIndicator.isHidden = true 81 | self.playButton.setImage(UIImage(named:"playerror"), for: .normal) 82 | // TODO: why doesn't this turn red? The playButton stays black for some reason. It works in MediaPlayerVC tho... 83 | self.playButton.tintColor = UIColor.red 84 | self.playButton.isHidden = false 85 | } else if audioPlayer.state == .playing && !self.pvMediaPlayer.shouldSetupClip { 86 | self.activityIndicator.isHidden = true 87 | self.playButton.setImage(UIImage(named:"pause"), for: .normal) 88 | self.playButton.tintColor = UIColor.black 89 | self.playButton.isHidden = false 90 | } else if audioPlayer.state == .buffering || self.pvMediaPlayer.shouldSetupClip { 91 | self.activityIndicator.isHidden = false 92 | self.playButton.isHidden = true 93 | } else { 94 | self.activityIndicator.isHidden = true 95 | self.playButton.setImage(UIImage(named:"play"), for: .normal) 96 | self.playButton.tintColor = UIColor.black 97 | self.playButton.isHidden = false 98 | } 99 | } 100 | } 101 | 102 | } 103 | 104 | extension NowPlayingBar:PVMediaPlayerUIDelegate { 105 | 106 | func playerHistoryItemBuffering() { 107 | self.togglePlayIcon() 108 | } 109 | 110 | func playerHistoryItemErrored() { 111 | self.togglePlayIcon() 112 | } 113 | 114 | func playerHistoryItemLoaded() { 115 | self.togglePlayIcon() 116 | } 117 | 118 | func playerHistoryItemLoadingBegan() { 119 | self.togglePlayIcon() 120 | } 121 | 122 | func playerHistoryItemPaused() { 123 | self.togglePlayIcon() 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /Podverse/PVPlayerHistoryManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PVPlayerHistoryManager.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 5/21/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class PlayerHistory { 12 | static let manager = PlayerHistory() 13 | var historyItems = [PlayerHistoryItem]() { 14 | didSet { 15 | self.saveData() 16 | } 17 | } 18 | 19 | //save data 20 | func saveData() { 21 | let data = NSMutableData() 22 | let archiver = NSKeyedArchiver(forWritingWith: data) 23 | archiver.encode(historyItems, forKey: "userHistory") 24 | archiver.finishEncoding() 25 | data.write(toFile: dataFilePath(), atomically: true) 26 | } 27 | 28 | //read data 29 | func loadData() { 30 | let path = self.dataFilePath() 31 | let defaultManager = FileManager() 32 | if defaultManager.fileExists(atPath: path) { 33 | let url = URL(fileURLWithPath: path) 34 | do { 35 | let data = try Data(contentsOf: url) 36 | let unarchiver = NSKeyedUnarchiver(forReadingWith: data) 37 | if let hItems = unarchiver.decodeObject(forKey: "userHistory") as? Array { 38 | historyItems = hItems 39 | } 40 | unarchiver.finishDecoding() 41 | } catch { 42 | print("Decoding failed: \(error.localizedDescription)") 43 | } 44 | 45 | 46 | } 47 | } 48 | 49 | func documentsDirectory()->String { 50 | let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, 51 | .userDomainMask, true) 52 | return paths.first ?? "" 53 | } 54 | 55 | func dataFilePath ()->String{ 56 | return self.documentsDirectory().appendingFormat("/.plist") 57 | } 58 | 59 | func addOrUpdateItem(item: PlayerHistoryItem?) { 60 | 61 | if let item = item { 62 | let previousIndex = historyItems.index(where: { (previousItem) -> Bool in // thanks sschuth https://stackoverflow.com/a/24069331/2608858 63 | previousItem.episodeMediaUrl == item.episodeMediaUrl 64 | }) 65 | 66 | if let index = previousIndex { 67 | historyItems[index] = item 68 | historyItems.rearrange(from: index, to: 0) 69 | } else { 70 | historyItems.insert(item, at: 0) 71 | } 72 | } 73 | 74 | } 75 | 76 | func retrieveExistingPlayerHistoryItem(mediaUrl: String) -> PlayerHistoryItem? { 77 | 78 | let previousIndex = historyItems.index(where: { (previousItem) -> Bool in // thanks sschuth https://stackoverflow.com/a/24069331/2608858 79 | previousItem.episodeMediaUrl == mediaUrl 80 | }) 81 | 82 | if let index = previousIndex { 83 | return historyItems[index] 84 | } 85 | 86 | return nil 87 | 88 | } 89 | 90 | func convertSearchPodcastEpisodeToPlayerHistoryItem(searchPodcast: SearchPodcast, searchEpisode: SearchEpisode) -> PlayerHistoryItem { 91 | let playerHistoryItem = PlayerHistoryItem( 92 | podcastId: searchPodcast.id, 93 | podcastFeedUrl: nil, // Since it is a searchPodcast, we can use podcastId instead of podcastFeedUrl 94 | podcastTitle: searchPodcast.title, 95 | podcastImageUrl: searchPodcast.imageUrl, 96 | episodeId: searchEpisode.id, 97 | episodeMediaUrl: searchEpisode.mediaUrl, 98 | episodeTitle: searchEpisode.title, 99 | episodeSummary: searchEpisode.summary, 100 | episodePubDate: searchEpisode.pubDate?.toServerDate(), 101 | hasReachedEnd: false, 102 | lastPlaybackPosition: 0) 103 | 104 | return playerHistoryItem 105 | } 106 | 107 | func convertEpisodeToPlayerHistoryItem(episode: Episode) -> PlayerHistoryItem { 108 | let playerHistoryItem = PlayerHistoryItem( 109 | podcastId: episode.podcast.id, 110 | podcastFeedUrl: episode.podcast.feedUrl, 111 | podcastTitle: episode.podcast.title, 112 | podcastImageUrl: episode.podcast.imageUrl, 113 | episodeMediaUrl: episode.mediaUrl, 114 | episodeTitle: episode.title, 115 | episodeSummary: episode.summary, 116 | episodePubDate: episode.pubDate, 117 | hasReachedEnd: false, 118 | lastPlaybackPosition: 0) 119 | 120 | return playerHistoryItem 121 | } 122 | 123 | func convertMediaRefToPlayerHistoryItem(mediaRef: MediaRef) -> PlayerHistoryItem { 124 | let playerHistoryItem = PlayerHistoryItem( 125 | mediaRefId: mediaRef.id, 126 | podcastId: mediaRef.podcastId, 127 | podcastFeedUrl: mediaRef.podcastFeedUrl, 128 | podcastTitle: mediaRef.podcastTitle, 129 | podcastImageUrl: mediaRef.podcastImageUrl, 130 | episodeId: mediaRef.episodeId, 131 | episodeMediaUrl: mediaRef.episodeMediaUrl, 132 | episodeTitle: mediaRef.episodeTitle, 133 | episodeSummary: mediaRef.episodeSummary, 134 | episodePubDate: mediaRef.episodePubDate, 135 | startTime: mediaRef.startTime, 136 | endTime: mediaRef.endTime, 137 | clipTitle: mediaRef.title, 138 | ownerName: mediaRef.ownerName, 139 | ownerId: mediaRef.ownerId, 140 | hasReachedEnd: false, 141 | lastPlaybackPosition: 0, 142 | isPublic: mediaRef.isPublic) 143 | 144 | return playerHistoryItem 145 | } 146 | 147 | func checkIfPodcastWasLastPlayed(podcastId:String?, feedUrl:String?) -> Bool { 148 | if let _ = podcastId, historyItems.first?.podcastId == podcastId { 149 | return true 150 | } else if let feedUrl = feedUrl, historyItems.first?.podcastFeedUrl == feedUrl { 151 | return true 152 | } else { 153 | return false 154 | } 155 | } 156 | 157 | func checkIfEpisodeWasLastPlayed(mediaUrl: String) -> Bool { 158 | if historyItems.first?.episodeMediaUrl == mediaUrl { 159 | return true 160 | } else { 161 | return false 162 | } 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /Podverse/PVReachability.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PVReachability.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/25/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReachabilitySwift 11 | 12 | class PVReachability { 13 | static let shared = PVReachability() 14 | let reachability = Reachability()! 15 | 16 | init() { 17 | reachability.whenReachable = { reachability in 18 | if !reachability.isReachableViaWiFi 19 | { 20 | // self.pauseDownloadingEpisodesUntilWiFi() 21 | } else { 22 | // self.resumeDownloadingEpisodes() 23 | } 24 | 25 | if UserDefaults.standard.object(forKey: "ONE_TIME_LOGIN") != nil && UserDefaults.standard.bool(forKey: "DefaultPlaylistsCreated") == false { 26 | // TODO: 27 | // let playlistManager = PlaylistManager.sharedInstance 28 | // playlistManager.getMyPlaylistsFromServer({ 29 | // playlistManager.createDefaultPlaylists() 30 | // }) 31 | } 32 | } 33 | 34 | reachability.whenUnreachable = { reachability in 35 | if !reachability.isReachableViaWiFi { 36 | // self.pauseDownloadingEpisodesUntilWiFi() 37 | } 38 | 39 | DispatchQueue.main.async { 40 | NotificationCenter.default.post(name:NSNotification.Name(rawValue: kInternetIsUnreachable), object: self, userInfo: nil) 41 | } 42 | 43 | } 44 | 45 | do { 46 | try reachability.startNotifier() 47 | } catch { 48 | print("Unable to start reachability notifier") 49 | } 50 | } 51 | 52 | func hasInternetConnection() -> Bool { 53 | return reachability.isReachable 54 | } 55 | 56 | func hasWiFiConnection() -> Bool { 57 | return reachability.isReachableViaWiFi 58 | } 59 | 60 | // TODO: move to PVDownloader without causing splash screen to hang indefinitely 61 | // func pauseDownloadingEpisodesUntilWiFi() { 62 | // let downloader = PVDownloader.shared 63 | // downloader.downloadSession.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in 64 | // for downloadingEpisode in DownloadingEpisodeList.shared.downloadingEpisodes { 65 | // if let taskIdentifier = downloadingEpisode.taskIdentifier { 66 | // for episodeDownloadTask in downloadTasks { 67 | // if episodeDownloadTask.taskIdentifier == taskIdentifier { 68 | // downloader.pauseOrResumeDownloadingEpisode(episode: downloadingEpisode) 69 | // } 70 | // } 71 | // } 72 | // } 73 | // } 74 | // } 75 | 76 | // TODO: move to PVDownloader without causing splash screen to hang indefinitely 77 | // func resumeDownloadingEpisodes() { 78 | // let downloader = PVDownloader.shared 79 | // downloader.downloadSession.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in 80 | // for downloadingEpisode in DownloadingEpisodeList.shared.downloadingEpisodes { 81 | // if (downloadingEpisode.taskResumeData != nil || downloadingEpisode.pausedWithoutResumeData == true) && downloadingEpisode.pausedByUser == false { 82 | // downloader.pauseOrResumeDownloadingEpisode(episode: downloadingEpisode) 83 | // } 84 | // } 85 | // } 86 | // } 87 | 88 | func createInternetConnectionNeededAlertWithDescription(_ message: String) -> UIAlertController { 89 | let connectionNeededAlert = UIAlertController(title: "Internet Connection Needed", message: message, preferredStyle: UIAlertControllerStyle.alert) 90 | connectionNeededAlert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) 91 | connectionNeededAlert.addAction(UIAlertAction(title: "Settings", style: .default) { (_) -> Void in 92 | let settingsURL = URL(string: UIApplicationOpenSettingsURLString) 93 | if let url = settingsURL { 94 | UIApplication.shared.open(url) 95 | } 96 | }) 97 | return connectionNeededAlert 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Podverse/PVSubscriber.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PVSubscriber.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 5/6/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class PVSubscriber { 13 | 14 | static func subscribeToPodcast(podcastId: String?, feedUrl: String?) { 15 | 16 | if let feedUrl = feedUrl { 17 | 18 | // TODO: add error handling / connectivity error 19 | updatePodcastOnServer(podcastId: podcastId, shouldSubscribe: true) { wasSuccessful in 20 | // 21 | } 22 | 23 | let feedParser = PVFeedParser(shouldOnlyGetMostRecentEpisode: false, shouldSubscribe: true, podcastId: podcastId) 24 | feedParser.addToParsingQueue(feedUrlString: feedUrl) 25 | } 26 | 27 | } 28 | 29 | static func unsubscribeFromPodcast(podcastId: String?, feedUrl: String?) { 30 | 31 | if let podcastId = podcastId { 32 | 33 | // TODO: add error handling / connectivity error 34 | updatePodcastOnServer(podcastId: podcastId, shouldSubscribe: false) { wasSuccessful in 35 | // 36 | } 37 | 38 | PVDeleter.deletePodcast(podcastId: podcastId, feedUrl: feedUrl) 39 | 40 | } else if let feedUrl = feedUrl { 41 | PVDeleter.deletePodcast(podcastId: nil, feedUrl: feedUrl) 42 | } 43 | 44 | } 45 | 46 | static func checkIfSubscribed(podcastId: String?) -> Bool { 47 | if let podcastId = podcastId, let _ = Podcast.podcastForId(id: podcastId) { 48 | return true 49 | } else { 50 | return false 51 | } 52 | } 53 | 54 | static func updatePodcastOnServer(podcastId:String?, shouldSubscribe:Bool, completion: @escaping (_ wasSuccessful:Bool?) -> Void) { 55 | 56 | let urlEnding = shouldSubscribe == true ? "subscribe" : "unsubscribe" 57 | 58 | if let podcastId = podcastId, let url = URL(string: BASE_URL + "podcasts/" + urlEnding) { 59 | var request = URLRequest(url: url, cachePolicy: NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30) 60 | request.httpMethod = "POST" 61 | 62 | guard let idToken = UserDefaults.standard.string(forKey: "idToken") else { 63 | DispatchQueue.main.async { 64 | completion(false) 65 | } 66 | return 67 | } 68 | 69 | request.setValue(idToken, forHTTPHeaderField: "Authorization") 70 | 71 | let postString = "podcastId=" + podcastId 72 | request.httpBody = postString.data(using: .utf8) 73 | 74 | showNetworkActivityIndicator() 75 | 76 | let task = URLSession.shared.dataTask(with: request) { data, response, error in 77 | 78 | hideNetworkActivityIndicator() 79 | 80 | guard error == nil else { 81 | DispatchQueue.main.async { 82 | completion(false) 83 | } 84 | return 85 | } 86 | 87 | DispatchQueue.main.async { 88 | completion(true) 89 | } 90 | 91 | } 92 | 93 | task.resume() 94 | 95 | } 96 | 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /Podverse/PVTimeHelpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PVTimeHelpers.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/24/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class PVTimeHelper { 12 | 13 | static func convertIntToHMSString (time : Int?) -> String { 14 | 15 | guard let time = time else { 16 | return "" 17 | } 18 | 19 | var hours = String(time / 3600) + ":" 20 | if hours == "0:" { 21 | hours = "" 22 | } 23 | var minutes = String((time / 60) % 60) + ":" 24 | if minutes.count < 3 && hours != "" { 25 | minutes = "0" + minutes 26 | } 27 | var seconds = String(time % 60) 28 | if seconds.count < 2 && (hours != "" || minutes != "") { 29 | seconds = "0" + seconds 30 | } 31 | 32 | return "\(hours)\(minutes)\(seconds)" 33 | } 34 | 35 | static func convertHMSStringToInt(hms : String) -> Int { 36 | var hmsComponents = hms.components(separatedBy:":").reversed().map() { String($0) } 37 | var seconds = 0 38 | var minutes = 0 39 | var hours = 0 40 | if let secondsVal = hmsComponents.first, let sec = Int(secondsVal) { 41 | seconds = sec 42 | hmsComponents.removeFirst() 43 | } 44 | 45 | if let minutesVal = hmsComponents.first, let min = Int(minutesVal) { 46 | minutes = min 47 | hmsComponents.removeFirst() 48 | } 49 | 50 | if let hoursVal = hmsComponents.first, let hr = Int(hoursVal) { 51 | hours = hr 52 | hmsComponents.removeFirst() 53 | } 54 | 55 | return convertHMSIntsToSeconds(hms:(hours, minutes, seconds)) 56 | } 57 | 58 | 59 | static func convertIntToHMSInts (seconds : Int) -> (Int, Int, Int) { 60 | return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60) 61 | } 62 | 63 | static func convertHMSIntsToSeconds(hms:(Int,Int,Int)) -> Int { 64 | let hoursInSeconds = hms.0 * 3600 65 | let minutesInSeconds = hms.2 * 60 66 | let totalSeconds = hoursInSeconds + minutesInSeconds + hms.2 67 | 68 | return totalSeconds 69 | } 70 | 71 | static func convertIntToReadableHMSDuration(seconds: Int) -> String { 72 | var string = "" 73 | let hmsInts = convertIntToHMSInts(seconds: seconds) 74 | 75 | if hmsInts.0 > 0 { 76 | string += String(hmsInts.0) + "h " 77 | } 78 | 79 | if hmsInts.1 > 0 { 80 | string += String(hmsInts.1) + "m " 81 | } 82 | 83 | if hmsInts.2 > 0 { 84 | string += String(hmsInts.2) + "s" 85 | } 86 | 87 | return string.trimmingCharacters(in: .whitespaces) 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /Podverse/PVViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PVViewController.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 5/8/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import StreamingKit 11 | import SDWebImage 12 | 13 | protocol TableViewHeightProtocol:class { 14 | func adjustTableView() 15 | } 16 | 17 | class PVViewController: UIViewController { 18 | 19 | let playerHistoryManager = PlayerHistory.manager 20 | let pvMediaPlayer = PVMediaPlayer.shared 21 | static weak var delegate:TableViewHeightProtocol? 22 | 23 | override func viewDidAppear(_ animated: Bool) { 24 | super.viewDidAppear(animated) 25 | } 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | self.navigationItem.backBarButtonItem = UIBarButtonItem(title:"", style:.plain, target:nil, action:nil) 30 | addObservers() 31 | 32 | } 33 | 34 | override func viewWillAppear(_ animated: Bool) { 35 | super.viewWillAppear(animated) 36 | pvMediaPlayer.delegate = self.tabBarController?.playerView 37 | PVViewController.delegate = self 38 | toggleNowPlayingBar() 39 | } 40 | 41 | deinit { 42 | removeObservers() 43 | } 44 | 45 | fileprivate func addObservers() { 46 | NotificationCenter.default.addObserver(self, selector: #selector(self.episodeDeleted(_:)), name: .episodeDeleted, object: nil) 47 | NotificationCenter.default.addObserver(self, selector: #selector(self.podcastDeleted(_:)), name: .podcastDeleted, object: nil) 48 | } 49 | 50 | fileprivate func removeObservers() { 51 | NotificationCenter.default.removeObserver(self, name: .episodeDeleted, object: nil) 52 | NotificationCenter.default.removeObserver(self, name: .podcastDeleted, object: nil) 53 | } 54 | 55 | func toggleNowPlayingBar() { 56 | self.updateNowPlayingBarData() { shouldShow in 57 | if shouldShow { 58 | self.tabBarController?.showPlayerView() 59 | } else { 60 | self.tabBarController?.hidePlayerView() 61 | } 62 | } 63 | } 64 | 65 | func hideNowPlayingBar() { 66 | self.tabBarController?.hidePlayerView() 67 | } 68 | 69 | func updateNowPlayingBarData(completion: @escaping (_ shouldShow: Bool) -> Void) { 70 | DispatchQueue.main.async { 71 | guard let currentItem = self.playerHistoryManager.historyItems.first, let tabbarVC = self.tabBarController, PVMediaPlayer.shared.nowPlayingItem != nil && currentItem.hasReachedEnd != true else { 72 | completion(false) 73 | return 74 | } 75 | 76 | tabbarVC.playerView.podcastTitleLabel.text = currentItem.podcastTitle?.stringByDecodingHTMLEntities() 77 | tabbarVC.playerView.episodeTitle.text = currentItem.episodeTitle?.stringByDecodingHTMLEntities() 78 | 79 | tabbarVC.playerView.podcastImageView.image = Podcast.retrievePodcastImage(podcastImageURLString: currentItem.podcastImageUrl, feedURLString: currentItem.podcastFeedUrl, completion: { image in 80 | tabbarVC.playerView.podcastImageView.image = image 81 | }) 82 | 83 | tabbarVC.playerView.togglePlayIcon() 84 | 85 | completion(true) 86 | } 87 | } 88 | 89 | func goToNowPlaying() { 90 | self.tabBarController?.goToNowPlaying() 91 | } 92 | 93 | } 94 | 95 | extension PVViewController { 96 | @objc func episodeDeleted(_ notification:Notification) { 97 | if let mediaUrl = notification.userInfo?["mediaUrl"] as? String { 98 | if playerHistoryManager.checkIfEpisodeWasLastPlayed(mediaUrl: mediaUrl) == true { 99 | DispatchQueue.main.async { 100 | self.tabBarController?.hidePlayerView() 101 | } 102 | } 103 | } 104 | } 105 | 106 | @objc func podcastDeleted(_ notification:Notification) { 107 | 108 | if let podcastId = notification.userInfo?["podcastId"] as? String, playerHistoryManager.checkIfPodcastWasLastPlayed(podcastId: podcastId, feedUrl: nil) == true { 109 | DispatchQueue.main.async { 110 | self.tabBarController?.hidePlayerView() 111 | } 112 | } 113 | 114 | 115 | if let feedUrl = notification.userInfo?["feedUrl"] as? String, playerHistoryManager.checkIfPodcastWasLastPlayed(podcastId: nil, feedUrl: feedUrl) == true { 116 | DispatchQueue.main.async { 117 | self.tabBarController?.hidePlayerView() 118 | } 119 | } 120 | } 121 | } 122 | 123 | extension PVViewController:TableViewHeightProtocol { 124 | func adjustTableView() { 125 | if let index = self.view.constraints.index(where: {$0.secondItem is UITableView && $0.secondAttribute == NSLayoutAttribute.bottom }), 126 | let tabbarVC = self.tabBarController { 127 | self.view.constraints[index].constant = tabbarVC.playerView.isHidden ? 0.0 : tabbarVC.playerView.frame.height - 0.5 128 | } else if let index = self.view.constraints.index(where: {$0.secondItem is UITableView && $0.secondAttribute == NSLayoutAttribute.bottom }) { 129 | self.view.constraints[index].constant = self.tabBarController?.tabBar.frame.height ?? 0.0 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Podverse/ParsingPodcasts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParsingPodcastUrls.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 9/19/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import TaskQueue 12 | 13 | final class ParsingPodcasts { 14 | static let shared = ParsingPodcasts() 15 | var podcastKeys = [String]() { 16 | didSet { 17 | DispatchQueue.main.async { 18 | UIApplication.shared.isNetworkActivityIndicatorVisible = (self.podcastKeys.count > 0) 19 | } 20 | } 21 | } 22 | var currentlyParsingItem = 0 23 | 24 | let queue = TaskQueue() 25 | 26 | init() { 27 | self.queue.maximumNumberOfActiveTasks = 2 28 | } 29 | 30 | func clearParsingPodcastsIfFinished() { 31 | if currentlyParsingItem == podcastKeys.count { 32 | currentlyParsingItem = 0 33 | self.podcastKeys.removeAll() 34 | DispatchQueue.main.async { 35 | NotificationCenter.default.post(name:NSNotification.Name(rawValue: kFinishedAllParsingPodcasts), object: self, userInfo: nil) 36 | } 37 | } 38 | } 39 | 40 | func addPodcast(podcastId:String?, feedUrl:String?) { 41 | if let podcastId = podcastId, self.podcastKeys.filter({$0 == podcastId}).count < 1 { 42 | self.podcastKeys.append(podcastId) 43 | DispatchQueue.main.async { 44 | NotificationCenter.default.post(name:NSNotification.Name(rawValue: kBeginParsingPodcast), object: self, userInfo: nil) 45 | } 46 | } else if let feedUrl = feedUrl, self.podcastKeys.filter({$0 == feedUrl}).count < 1, podcastId == nil { 47 | self.podcastKeys.append(feedUrl) 48 | DispatchQueue.main.async { 49 | NotificationCenter.default.post(name:NSNotification.Name(rawValue: kBeginParsingPodcast), object: self, userInfo: nil) 50 | } 51 | } 52 | } 53 | 54 | func removePodcast(podcastId:String?, feedUrl:String?) { 55 | if let podcastId = podcastId, let index = self.podcastKeys.index(of: podcastId) { 56 | self.podcastKeys.remove(at: index) 57 | DispatchQueue.main.async { 58 | NotificationCenter.default.post(name:NSNotification.Name(rawValue: kFinishedParsingPodcast), object: self, userInfo: nil) 59 | } 60 | } else if let feedUrl = feedUrl, let index = self.podcastKeys.index(of: feedUrl) { 61 | self.podcastKeys.remove(at: index) 62 | DispatchQueue.main.async { 63 | NotificationCenter.default.post(name:NSNotification.Name(rawValue: kFinishedParsingPodcast), object: self, userInfo: nil) 64 | } 65 | } 66 | } 67 | 68 | func hasMatchingId(podcastId:String?) -> Bool { 69 | if let podcastId = podcastId, let _ = self.podcastKeys.index(of: podcastId) { 70 | return true 71 | } 72 | 73 | return false 74 | } 75 | 76 | func hasMatchingUrl(feedUrl:String) -> Bool { 77 | if let _ = self.podcastKeys.index(of: feedUrl) { 78 | return true 79 | } 80 | 81 | return false 82 | } 83 | 84 | func podcastFinishedParsing() { 85 | self.currentlyParsingItem += 1 86 | clearParsingPodcastsIfFinished() 87 | DispatchQueue.main.async { 88 | NotificationCenter.default.post(name:NSNotification.Name(rawValue: kFinishedParsingPodcast), object: self, userInfo: nil) 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /Podverse/PlaylistDetailTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlaylistDetailTableViewCell.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 8/23/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PlaylistDetailTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var clipTitle: UILabel! 14 | @IBOutlet weak var episodeTitle: UILabel! 15 | @IBOutlet weak var podcastImage: UIImageView! 16 | @IBOutlet weak var podcastTitle: UILabel! 17 | @IBOutlet weak var pubDate: UILabel! 18 | @IBOutlet weak var time: UILabel! 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Podverse/PlaylistTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlaylistTableViewCell.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 8/22/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PlaylistTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var itemCount: UILabel! 14 | @IBOutlet weak var lastUpdated: UILabel! 15 | @IBOutlet weak var title: UILabel! 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Podverse/PodcastSearchResultTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PodcastSearchResultTableViewCell.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 7/9/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PodcastSearchResultTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var pvImage: UIImageView! 14 | @IBOutlet weak var title: UILabel! 15 | @IBOutlet weak var hosts: UILabel! 16 | @IBOutlet weak var categories: UILabel! 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Podverse/PodcastTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PodcastTableViewCell.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/27/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PodcastTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var autoDownloadIndicator: UILabel! 14 | @IBOutlet weak var lastPublishedDate: UILabel! 15 | @IBOutlet weak var pvImage: UIImageView! 16 | @IBOutlet weak var title: UILabel! 17 | @IBOutlet weak var totalEpisodes: UILabel! 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Podverse/Podverse.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.associated-domains 6 | 7 | applinks:podverse.fm 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Podverse/SearchCategory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchCategory.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 10/23/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | //import Foundation 10 | // 11 | //class SearchCategory { 12 | // 13 | // var id:Int64? 14 | // var name:String = "" 15 | // var parent_id:Int64? 16 | // 17 | // static func convertJSONToSearchCategories(_ json: AnyObject) -> [SearchCategory]? { 18 | // 19 | // var categories = [SearchCategory]() 20 | // 21 | // if let items = json as? [AnyObject] { 22 | // for item in items { 23 | // let category = SearchCategory() 24 | // category.id = item["id"] as? Int64 25 | // category.name = item["name"] as? String ?? "" 26 | // category.parent_id = item["parent_id"] as? Int64 27 | // categories.append(category) 28 | // } 29 | // } 30 | // 31 | // return categories 32 | // 33 | // } 34 | // 35 | // static func retrieveCategoriesFromServer(parentId: Int64?, _ completion: @escaping (_ categories:[SearchCategory]?) -> Void) { 36 | // 37 | // SearchClientSwift.retrieveCategories({ serviceResponse in 38 | // if let response = serviceResponse.0, let categories = SearchCategory.convertJSONToSearchCategories(response) { 39 | // completion(categories) 40 | // } 41 | // 42 | // if let error = serviceResponse.1 { 43 | // print(error.localizedDescription) 44 | // completion(nil) 45 | // } 46 | // 47 | // }) 48 | // 49 | // } 50 | // 51 | // static func filterCategories(categories: [SearchCategory] , parentId: Int64?) -> [SearchCategory] { 52 | // 53 | // var filteredCategories = [SearchCategory]() 54 | // 55 | // if let parentId = parentId { 56 | // filteredCategories = categories.filter { $0.parent_id == parentId } 57 | // } else { 58 | // filteredCategories = categories.filter { $0.parent_id == nil } 59 | // } 60 | // 61 | // filteredCategories.sort(by: { $0.name < $1.name }) 62 | // 63 | // return filteredCategories 64 | // 65 | // } 66 | // 67 | //} 68 | // 69 | -------------------------------------------------------------------------------- /Podverse/SearchEpisode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchEpisode.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 1/25/18. 6 | // Copyright © 2018 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class SearchEpisode { 12 | 13 | var id: String? 14 | var isPublic: Bool = false 15 | var duration: NSNumber? 16 | var mediaUrl: String? 17 | var pubDate: String? 18 | var summary: String? 19 | var title: String? 20 | 21 | static func convertJSONToSearchEpisode (_ json: [String:Any]) -> SearchEpisode { 22 | 23 | let searchEpisode = SearchEpisode() 24 | 25 | if let id = json["id"] as? String { 26 | searchEpisode.id = id 27 | } 28 | 29 | if let isPublic = json["isPublic"] as? Bool { 30 | searchEpisode.isPublic = isPublic 31 | } 32 | 33 | if let mediaUrl = json["mediaUrl"] as? String { 34 | searchEpisode.mediaUrl = mediaUrl 35 | } 36 | 37 | if let pubDate = json["pubDate"] as? String { 38 | searchEpisode.pubDate = pubDate 39 | } 40 | 41 | if let summary = json["summary"] as? String { 42 | searchEpisode.summary = summary 43 | } 44 | 45 | if let title = json["title"] as? String { 46 | searchEpisode.title = title 47 | } 48 | 49 | if let duration = json["duration"] as? NSNumber { 50 | searchEpisode.duration = duration 51 | } 52 | 53 | return searchEpisode 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Podverse/SearchNetwork.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchNetwork.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 10/23/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | //import Foundation 10 | // 11 | //class SearchNetwork { 12 | // 13 | // var id:Int64? 14 | // var name:String? 15 | // 16 | // static func convertJSONToSearchNetworks(_ json: AnyObject) -> [SearchNetwork]? { 17 | // 18 | // var networks = [SearchNetwork]() 19 | // 20 | // if let items = json as? [AnyObject] { 21 | // for item in items { 22 | // let network = SearchNetwork() 23 | // network.id = item["id"] as? Int64 24 | // network.name = item["name"] as? String 25 | // networks.append(network) 26 | // } 27 | // } 28 | // 29 | // return networks 30 | // 31 | // } 32 | // 33 | // static func retrieveNetworksFromServer(_ completion: @escaping (_ networks:[SearchNetwork]?) -> Void) { 34 | // 35 | // SearchClientSwift.retrieveNetworks({ serviceResponse in 36 | // 37 | // if let response = serviceResponse.0, let networks = SearchNetwork.convertJSONToSearchNetworks(response) { 38 | // completion(networks) 39 | // } else if let error = serviceResponse.1 { 40 | // print(error.localizedDescription) 41 | // completion(nil) 42 | // } 43 | // 44 | // }) 45 | // 46 | // } 47 | // 48 | //} 49 | 50 | -------------------------------------------------------------------------------- /Podverse/SettingsTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsTableViewController.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 7/3/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SettingsTableViewController: UITableViewController { 12 | 13 | @IBOutlet weak var allowCellularDataSwitch: UISwitch! 14 | @IBOutlet weak var enableAutoDownloadSwitch: UISwitch! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | self.title = "Settings" 20 | self.tableView.alwaysBounceVertical = false 21 | self.allowCellularDataSwitch.isOn = UserDefaults.standard.bool(forKey: kAllowCellularDataDownloads) 22 | self.enableAutoDownloadSwitch.isOn = UserDefaults.standard.bool(forKey: kEnableAutoDownloadByDefault) 23 | } 24 | 25 | @IBAction func allowCellularDataAction(_ sender: Any) { 26 | UserDefaults.standard.set(allowCellularDataSwitch.isOn, forKey: kAllowCellularDataDownloads) 27 | } 28 | 29 | @IBAction func enableAutoDownloadAction(_ sender: Any) { 30 | UserDefaults.standard.set(enableAutoDownloadSwitch.isOn, forKey: kEnableAutoDownloadByDefault) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Podverse/String+CharacterManipulation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+CharacterManipulation.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/27/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension String { 13 | 14 | func formatHtmlString(isWhiteBg: Bool = false) -> String { 15 | 16 | var style = "" 17 | 18 | if isWhiteBg { 19 | style = "" 20 | } else { 21 | style = "" 22 | } 23 | 24 | return style + self 25 | } 26 | 27 | func removeArticles() -> String { 28 | var words = self.components(separatedBy: " ") 29 | 30 | //Only one word so count it as sortable 31 | if(words.count <= 1) { 32 | return self 33 | } 34 | 35 | if( words[0].lowercased() == "a" || words[0].lowercased() == "the" || words[0].lowercased() == "an" ) { 36 | words.removeFirst() 37 | return words.joined(separator: " ") 38 | } 39 | 40 | return self 41 | } 42 | 43 | func removeHTMLFromString() -> String? { 44 | return self.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression, range: nil) 45 | } 46 | 47 | func encodePipeInString () -> String { 48 | return self.replacingOccurrences(of:"|", with:"%7C", options: .literal, range: nil) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Podverse/String+ConvertPlaybackTimeToUrlSchemeElements.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+ConvertTimeStampsToUrlSchemeElements.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 7/22/18. 6 | // Copyright © 2018 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | func convertPlaybackTimesToUrlSchemeElements() -> String? { 14 | var newSelf = self 15 | 16 | do { 17 | let pattern = "([0-9][0-9]:[0-5][0-9]:[0-5][0-9]|[0-9]:[0-5][0-9]:[0-5][0-9]|[0-5][0-9]:[0-5][0-9]|[0-9]:[0-5][0-9])" 18 | let regex = try NSRegularExpression(pattern: pattern, options: []) 19 | let matches = regex.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) 20 | var indexOffset = 0 21 | 22 | for match in matches as [NSTextCheckingResult] { 23 | let location = match.range.location + indexOffset 24 | let length = match.range.length 25 | let range = NSMakeRange(location, length) 26 | if let strRange = Range(range, in: newSelf) { 27 | let urlSchemeElement = convertPlaybackTimeToUrlSchemeElement(timestamp: String(newSelf[strRange])) 28 | newSelf = newSelf.replacingCharacters(in: strRange, with: urlSchemeElement) 29 | indexOffset += urlSchemeElement.count - length 30 | } 31 | } 32 | } catch { 33 | print(error) 34 | } 35 | 36 | return newSelf 37 | } 38 | 39 | private func convertPlaybackTimeToUrlSchemeElement(timestamp: String) -> String { 40 | return "" + timestamp + "" 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Podverse/String+Formatters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Formatters.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/29/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | func toServerDate() -> Date? { 13 | let dateFormatter = DateFormatter() 14 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" 15 | return dateFormatter.date(from: self) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Podverse/String+MediaPlayerTimeToSeconds.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+MediaPlayerTimeToSeconds.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 7/22/18. 6 | // Copyright © 2018 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | func mediaPlayerTimeToSeconds() -> Int64 { 13 | let timeComponents = self.components(separatedBy: ":") 14 | var int = Int64(0) 15 | 16 | if timeComponents.count == 2, let minutes = Int64(timeComponents[0]), let seconds = Int64(timeComponents[1]) { 17 | int = minutes * 60 + seconds 18 | } else if timeComponents.count == 3, let hours = Int64(timeComponents[0]), let minutes = Int64(timeComponents[1]), let seconds = Int64(timeComponents[2]) { 19 | int = hours * 3600 + minutes * 60 + seconds 20 | } 21 | 22 | return int 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Podverse/String+RemoveHTMLCharacters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+RemoveHTMLCharacters.swift 3 | // 4 | // Created by Nacho on 29/10/14. 5 | // Copyright (c) 2014 Ignacio Nieto Carvajal. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | extension String { 12 | 13 | /** 14 | * A pretty basic and simple decoder that will change the HTML entities for UTF8 ready characters. 15 | */ 16 | func stringByDecodingHTMLEntities() -> String? { 17 | var r: NSRange 18 | let pattern = "<[^>]+>" 19 | var s = self.stringByDecodingHTMLEscapeCharacters() 20 | r = (s as NSString).range(of: pattern, options: NSString.CompareOptions.regularExpression) 21 | while (r.location != NSNotFound) { 22 | s = (s as NSString).replacingCharacters(in: r, with: " ") 23 | r = (s as NSString).range(of: pattern, options: NSString.CompareOptions.regularExpression) 24 | } 25 | return s.replacingOccurrences(of: " ", with: " ") 26 | } 27 | 28 | func stringByDecodingHTMLEscapeCharacters() -> String { 29 | var s = self.replacingOccurrences(of: """, with: "\"") 30 | s = s.replacingOccurrences(of: "'", with: "'") 31 | s = s.replacingOccurrences(of: "&", with: "&") 32 | s = s.replacingOccurrences(of: "<", with: "<") 33 | s = s.replacingOccurrences(of: ">", with: ">") 34 | s = s.replacingOccurrences(of: "'", with: "'") 35 | s = s.replacingOccurrences(of: "&ldquot;", with: "\"") 36 | s = s.replacingOccurrences(of: "&rdquot;", with: "\"") 37 | s = s.replacingOccurrences(of: " ", with: " ") 38 | s = s.replacingOccurrences(of: "á", with: "á") 39 | s = s.replacingOccurrences(of: "é", with: "é") 40 | s = s.replacingOccurrences(of: "í", with: "í") 41 | s = s.replacingOccurrences(of: "ó", with: "ó") 42 | s = s.replacingOccurrences(of: "ú", with: "ú") 43 | s = s.replacingOccurrences(of: "ñ", with: "ñ") 44 | s = s.replacingOccurrences(of: "’", with: "'") 45 | 46 | return s 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Podverse/String+Utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Utilities.swift 3 | // FeedParser 4 | // 5 | // Created by Ignacio Nieto Carvajal on 9/10/16. 6 | // Copyright © 2016 Ignacio Nieto Carvajal. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | func substringBetween(initial: String, final: String) -> String? { 13 | guard var startIndex = self.range(of: initial)?.lowerBound else { return nil } // initial string not found 14 | startIndex = self.index(startIndex, offsetBy: 1) 15 | guard let endIndex = self.range(of: final, options: [] , range: Range(uncheckedBounds: (startIndex, self.endIndex)), locale: nil)?.lowerBound else { return nil } // final string not found from initial. 16 | let range = Range(uncheckedBounds: (startIndex, endIndex)) 17 | return self.substring(with: range) 18 | } 19 | 20 | func toFailableBool() -> Bool? { 21 | switch self.lowercased() { 22 | case "true", "t", "yes", "y", "1": 23 | return true 24 | case "false", "f", "no", "n", "0": 25 | return false 26 | default: 27 | return nil 28 | } 29 | } 30 | 31 | func toBool() -> Bool { 32 | switch self.lowercased() { 33 | case "true", "t", "yes", "y", "1": 34 | return true 35 | case "false", "f", "no", "n", "0": 36 | return false 37 | default: 38 | return false 39 | } 40 | } 41 | 42 | func toDouble() -> Double { 43 | let numberFormatter = NumberFormatter() 44 | numberFormatter.locale = Locale(identifier: "en_US_POSIX") 45 | return numberFormatter.number(from: self.replacingOccurrences(of: ",", with: "."))?.doubleValue ?? 0.0 46 | } 47 | 48 | func toFailableDouble() -> Double? { 49 | let numberFormatter = NumberFormatter() 50 | numberFormatter.locale = Locale(identifier: "en_US_POSIX") 51 | return numberFormatter.number(from: self.replacingOccurrences(of: ",", with: "."))?.doubleValue ?? 0.0 52 | } 53 | 54 | func toInt() -> Int { 55 | return NumberFormatter().number(from: self)?.intValue ?? 0 56 | } 57 | 58 | func toFailableInt() -> Int? { 59 | return NumberFormatter().number(from: self)?.intValue 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Podverse/Supporting Libraries/OAuth2RetryHandler.swift: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podverse/podverse-ios/fa55a169670731bcbd9073f16f75014d9b74c0fd/Podverse/Supporting Libraries/OAuth2RetryHandler.swift -------------------------------------------------------------------------------- /Podverse/SyncablePodcast.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncablePodcast.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 1/20/18. 6 | // Copyright © 2018 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import Foundation 12 | import UIKit 13 | 14 | class SyncablePodcast { 15 | 16 | var feedUrl: String? 17 | var id: String? 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Podverse/UITabbarController+PlayerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITabbarController+PlayerView.swift 3 | // Podverse 4 | // 5 | // Created by Creon Creonopoulos on 7/16/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol PlayerViewProtocol { 12 | func setupPlayerBar() 13 | func hidePlayerView() 14 | func showPlayerView() 15 | func goToNowPlaying(isDataAvailable:Bool) 16 | var playerView:NowPlayingBar {get} 17 | } 18 | 19 | private var pvAssociationKey: UInt8 = 0 20 | 21 | extension UITabBarController:PlayerViewProtocol { 22 | 23 | var playerView:NowPlayingBar { 24 | get { 25 | return objc_getAssociatedObject(self, &pvAssociationKey) as! NowPlayingBar 26 | } 27 | set(newValue) { 28 | objc_setAssociatedObject(self, &pvAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) 29 | } 30 | } 31 | 32 | override open func viewDidLoad() { 33 | super.viewDidLoad() 34 | self.playerView = NowPlayingBar() 35 | setupPlayerBar() 36 | } 37 | 38 | func setupPlayerBar() { 39 | var extraIphoneXSpace:CGFloat = 0.0 40 | if (UIScreen.main.nativeBounds.height == 2436.0) { 41 | extraIphoneXSpace = 33.0 42 | } 43 | 44 | self.playerView.frame = CGRect(x: self.tabBar.frame.minX, 45 | y: self.tabBar.frame.minY - NowPlayingBar.playerHeight - extraIphoneXSpace, 46 | width: self.tabBar.frame.width, 47 | height: NowPlayingBar.playerHeight) 48 | self.playerView.isHidden = true 49 | self.view.addSubview(self.playerView) 50 | self.playerView.delegate = self 51 | } 52 | 53 | func hidePlayerView() { 54 | self.playerView.isHidden = true 55 | PVViewController.delegate?.adjustTableView() 56 | } 57 | 58 | func showPlayerView() { 59 | self.playerView.isHidden = false 60 | PVViewController.delegate?.adjustTableView() 61 | } 62 | 63 | func goToNowPlaying(isDataAvailable:Bool = true) { 64 | if let currentNavVC = self.selectedViewController?.childViewControllers.first?.navigationController { 65 | 66 | var optionalMediaPlayerVC: MediaPlayerViewController? 67 | 68 | for controller in currentNavVC.viewControllers { 69 | if controller.isKind(of: MediaPlayerViewController.self) { 70 | optionalMediaPlayerVC = controller as? MediaPlayerViewController 71 | break 72 | } 73 | } 74 | 75 | if let mediaPlayerVC = optionalMediaPlayerVC { 76 | currentNavVC.popToViewController(mediaPlayerVC, animated: false) 77 | } else if let mediaPlayerVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MediaPlayerVC") as? MediaPlayerViewController { 78 | PVMediaPlayer.shared.isDataAvailable = isDataAvailable 79 | if !isDataAvailable { 80 | PVMediaPlayer.shared.shouldAutoplayOnce = true 81 | } 82 | 83 | currentNavVC.pushViewController(mediaPlayerVC, animated: true) 84 | } 85 | } 86 | } 87 | 88 | func goToClips(_ clipFilter:ClipFilter? = nil, _ clipSorting:ClipSorting? = nil) { 89 | if let currentNavVC = self.selectedViewController?.childViewControllers.first?.navigationController { 90 | 91 | var optionalClipsTVC: ClipsTableViewController? 92 | 93 | for controller in currentNavVC.viewControllers { 94 | if controller.isKind(of: ClipsTableViewController.self) { 95 | optionalClipsTVC = controller as? ClipsTableViewController 96 | break 97 | } 98 | } 99 | 100 | if let clipFilter = clipFilter { 101 | UserDefaults.standard.set(clipFilter.rawValue, forKey: kClipsTableFilterType) 102 | } else { 103 | UserDefaults.standard.set(ClipFilter.allPodcasts.rawValue, forKey: kClipsTableFilterType) 104 | } 105 | 106 | if let clipSorting = clipSorting { 107 | UserDefaults.standard.set(clipSorting.rawValue, forKey: kClipsTableSortingType) 108 | } else { 109 | UserDefaults.standard.set(ClipSorting.topWeek.rawValue, forKey: kClipsTableFilterType) 110 | } 111 | 112 | if let clipsTVC = optionalClipsTVC { 113 | clipsTVC.shouldOverrideQuery = true 114 | currentNavVC.popToViewController(clipsTVC, animated: false) 115 | } else if let clipsTVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ClipsTVC") as? ClipsTableViewController { 116 | currentNavVC.pushViewController(clipsTVC, animated: false) 117 | } 118 | } 119 | } 120 | 121 | 122 | func goToPlaylistDetail(id:String) { 123 | if let currentNavVC = self.selectedViewController?.childViewControllers.first?.navigationController { 124 | 125 | if let playlistDetailVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PlaylistDetailVC") as? PlaylistDetailTableViewController { 126 | playlistDetailVC.playlistId = id 127 | playlistDetailVC.isDataAvailable = false 128 | currentNavVC.pushViewController(playlistDetailVC, animated: true) 129 | } 130 | } 131 | } 132 | 133 | func gotoSearchPodcastView(id:String) { 134 | if let currentNavVC = self.selectedViewController?.childViewControllers.first?.navigationController { 135 | 136 | if let searchPodcastVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SearchPodcastVC") as? SearchPodcastViewController { 137 | searchPodcastVC.podcastId = id 138 | currentNavVC.pushViewController(searchPodcastVC, animated: true) 139 | } 140 | } 141 | } 142 | } 143 | 144 | extension UITabBarController:NowPlayingBarDelegate { 145 | func didTapView() { 146 | goToNowPlaying() 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Podverse/UITableView+NotAvailableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+NotAvailableView.swift 3 | // Podverse 4 | // 5 | // Created by Creon Creonopoulos on 9/19/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIViewController { 12 | 13 | func showNoDataView() { 14 | let noDataView = self.view.viewWithTag(kNoDataViewTag) 15 | noDataView?.isHidden = false 16 | } 17 | 18 | func hideNoDataView() { 19 | let noDataView = self.view.viewWithTag(kNoDataViewTag) 20 | noDataView?.isHidden = true 21 | } 22 | 23 | func addNoDataViewWithMessage(_ message:String, buttonTitle:String? = nil, buttonImage:UIImage? = nil, retryPressed:Selector? = nil, isBlackBg:Bool = false) { 24 | let noDataView = UIView() 25 | let noDataTextLabel = UILabel() 26 | let actionButton = UIButton() 27 | 28 | noDataView.translatesAutoresizingMaskIntoConstraints = false 29 | noDataView.backgroundColor = isBlackBg ? .black : .white 30 | noDataView.tag = kNoDataViewTag 31 | actionButton.translatesAutoresizingMaskIntoConstraints = false 32 | noDataTextLabel.translatesAutoresizingMaskIntoConstraints = false 33 | 34 | noDataTextLabel.text = message 35 | noDataTextLabel.textColor = isBlackBg ? .white : .black 36 | noDataTextLabel.numberOfLines = 5 37 | noDataTextLabel.textAlignment = .center 38 | actionButton.setTitle(buttonTitle, for: .normal) 39 | actionButton.setTitleColor(.blue, for: .normal) 40 | 41 | if let image = buttonImage { 42 | actionButton.setImage(image, for: .normal) 43 | } 44 | 45 | if let retryAction = retryPressed { 46 | actionButton.addTarget(self, action: retryAction, for: .touchUpInside) 47 | } 48 | 49 | self.view.addSubview(noDataView) 50 | 51 | let containerLeading = NSLayoutConstraint( item:self.view, 52 | attribute:.leading, 53 | relatedBy:.equal, 54 | toItem:noDataView, 55 | attribute:.leading, 56 | multiplier:1, 57 | constant:0) 58 | 59 | let containerTrailing = NSLayoutConstraint( item:self.view, 60 | attribute:.trailing, 61 | relatedBy:.equal, 62 | toItem:noDataView, 63 | attribute:.trailing, 64 | multiplier:1, 65 | constant:0) 66 | 67 | let containerTop = NSLayoutConstraint( item:self.view, 68 | attribute:.top, 69 | relatedBy:.equal, 70 | toItem:noDataView, 71 | attribute:.top, 72 | multiplier:1, 73 | constant:0) 74 | 75 | let containerBottom = NSLayoutConstraint( item:self.view, 76 | attribute:.bottom, 77 | relatedBy:.equal, 78 | toItem:noDataView, 79 | attribute:.bottom, 80 | multiplier:1, 81 | constant:0) 82 | 83 | self.view.addConstraints([containerTop, containerBottom, containerLeading, containerTrailing]) 84 | 85 | noDataView.addSubview(actionButton) 86 | noDataView.addSubview(noDataTextLabel) 87 | 88 | 89 | var allConstraints = [NSLayoutConstraint]() 90 | 91 | let buttonHorizontalConstraint = NSLayoutConstraint( item:noDataView, 92 | attribute:.centerX, 93 | relatedBy:.equal, 94 | toItem:actionButton, 95 | attribute:.centerX, 96 | multiplier:1, 97 | constant:0) 98 | 99 | let buttonVerticalConstraint = NSLayoutConstraint( item:actionButton, 100 | attribute:.top, 101 | relatedBy:.equal, 102 | toItem:noDataTextLabel, 103 | attribute:.bottom, 104 | multiplier:1, 105 | constant:20) 106 | 107 | let labelVerticalConstraint = NSLayoutConstraint( item:noDataView, 108 | attribute:.centerY, 109 | relatedBy:.equal, 110 | toItem:noDataTextLabel, 111 | attribute:.centerY, 112 | multiplier:1, 113 | constant:0) 114 | 115 | let labelHorizontalConstraint = NSLayoutConstraint( item:noDataView, 116 | attribute:.centerX, 117 | relatedBy:.equal, 118 | toItem:noDataTextLabel, 119 | attribute:.centerX, 120 | multiplier:1, 121 | constant:0) 122 | 123 | let labelWidthConstraint = NSLayoutConstraint(item: noDataTextLabel, 124 | attribute: .width, 125 | relatedBy: .lessThanOrEqual, 126 | toItem: nil, 127 | attribute: .notAnAttribute, 128 | multiplier: 1, constant: 300) 129 | 130 | allConstraints.append(labelVerticalConstraint) 131 | allConstraints.append(labelHorizontalConstraint) 132 | allConstraints.append(labelWidthConstraint) 133 | allConstraints.append(buttonHorizontalConstraint) 134 | allConstraints.append(buttonVerticalConstraint) 135 | 136 | noDataView.addConstraints(allConstraints) 137 | 138 | self.view.sendSubview(toBack: noDataView) 139 | } 140 | } 141 | 142 | -------------------------------------------------------------------------------- /Podverse/UIViewController+AllowCellularDataDownloadsAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+AllowCellularDataDownloadsAlert.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 2/19/18. 6 | // Copyright © 2018 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIViewController { 12 | func showAllowCellularDataAlert(completion: ((Bool) -> ())? = nil) { 13 | UserDefaults.standard.set(true, forKey: kAskedToAllowCellularDataDownloads) 14 | 15 | let allowCellularDataAlert = UIAlertController(title: "Enable Cellular Data?", message: "Do you want to allow downloading episodes using cellular data when not connected to Wi-Fi?", preferredStyle: UIAlertControllerStyle.alert) 16 | 17 | allowCellularDataAlert.addAction(UIAlertAction(title: "Yes", style: .default) { (_) -> Void in 18 | UserDefaults.standard.set(true, forKey: kAllowCellularDataDownloads) 19 | completion?(true) 20 | }) 21 | 22 | allowCellularDataAlert.addAction(UIAlertAction(title: "No", style: .cancel) { (_) -> Void in 23 | UserDefaults.standard.set(false, forKey: kAllowCellularDataDownloads) 24 | completion?(false) 25 | }) 26 | 27 | DispatchQueue.main.async { 28 | self.present(allowCellularDataAlert, animated: true, completion: nil) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Podverse/UIViewController+InternetRequiredAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+InternetRequiredAlert.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/26/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIViewController { 12 | func showInternetNeededAlertWithDescription(message: String) { 13 | let internetNeededAlert = PVReachability.shared.createInternetConnectionNeededAlertWithDescription(message) 14 | DispatchQueue.main.async { 15 | self.present(internetNeededAlert, animated: true, completion: nil) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Podverse/UIViewController+ShowToast.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+ShowToast.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 11/21/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // Thanks to Mr. Bean https://stackoverflow.com/a/35130932/2608858 12 | extension UIViewController { 13 | 14 | func showToast(message : String) { 15 | 16 | let toastLabel = UILabel(frame: CGRect(x: self.view.frame.size.width/2 - 90, y: self.view.frame.size.height/2 - 30, width: 180, height: 40)) 17 | toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.6) 18 | toastLabel.textColor = UIColor.white 19 | toastLabel.textAlignment = .center; 20 | toastLabel.font = UIFont(name: "System", size: 16.0) 21 | toastLabel.text = message 22 | toastLabel.alpha = 1.0 23 | toastLabel.layer.cornerRadius = 5; 24 | toastLabel.clipsToBounds = true 25 | self.view.addSubview(toastLabel) 26 | UIView.animate(withDuration: 3.0, delay: 0.1, options: .curveEaseOut, animations: { 27 | toastLabel.alpha = 0.0 28 | }, completion: {(isCompleted) in 29 | toastLabel.removeFromSuperview() 30 | }) 31 | 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Podverse/URL+Converters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+Converters.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 8/1/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension URL { 12 | 13 | func convertToCustomUrl(scheme: String) -> URL? { 14 | 15 | guard var components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { return nil } 16 | 17 | if self.absoluteString.hasPrefix("http://") { 18 | components.scheme = "http" + scheme 19 | } else { 20 | components.scheme = "https" + scheme 21 | } 22 | 23 | return components.url 24 | 25 | } 26 | 27 | func convertToOriginalUrl() -> URL? { 28 | 29 | let actualUrlComponents = NSURLComponents(url: self, resolvingAgainstBaseURL: false) 30 | 31 | if self.scheme == "httpstreaming" { 32 | actualUrlComponents?.scheme = "http" 33 | } else if self.scheme == "httpsstreaming" { 34 | actualUrlComponents?.scheme = "https" 35 | } 36 | 37 | return actualUrlComponents?.url 38 | 39 | } 40 | 41 | } 42 | 43 | -------------------------------------------------------------------------------- /Podverse/URL+RemoteSize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+RemoteSize.swift 3 | // Podverse 4 | // 5 | // Created by Creon on 12/26/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension URL { 12 | var remoteSize: Int64 { 13 | var contentLength: Int64 = NSURLSessionTransferSizeUnknown 14 | var request = URLRequest(url: self, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData , timeoutInterval: 30.0); 15 | 16 | // I originally tried to derive expectedContentLength using httpMethod of "HEAD", but discovered that, while it worked for most podcsats, some nginx servers do not include Content Length in a response header. 17 | 18 | // Pod Save America is the podcast I discovered this issue with. Attempt to make a HEAD request with this link for an example https://rss.art19.com/episodes/92be9331-1cd4-4f93-88c7-c1f3139f2807.mp3 19 | 20 | // In order to work around this issue, I'm using a GET request instead, then overriding the NSURLSessionDataDelegate in PVStreamer to determine the content length, then immediately cancel the session for that GET request. 21 | 22 | request.httpMethod = "GET"; 23 | request.timeoutInterval = 5; 24 | let group = DispatchGroup() 25 | group.enter() 26 | 27 | URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) -> Void in 28 | contentLength = response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown 29 | group.leave() 30 | }).resume() 31 | _ = group.wait(timeout: DispatchTime.now() + .seconds(5)) 32 | return contentLength 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Podverse/URL+getQueryParamValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+getQueryParamValue.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 7/31/18. 6 | // Copyright © 2018 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension URL { 12 | func getQueryParamValue(_ param:String) -> String? { 13 | guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true) else { 14 | return nil 15 | } 16 | 17 | return components.queryItems?.first(where: { $0.name == param})?.value 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Podverse/Utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utilities.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 10/17/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | func checkForConnectivity() -> Bool { 13 | 14 | guard PVReachability.shared.hasInternetConnection() else { 15 | return false 16 | } 17 | 18 | return true 19 | 20 | } 21 | 22 | func checkForResults(results: [Any]?) -> Bool { 23 | 24 | guard let results = results, results.count > 0 else { 25 | return false 26 | } 27 | 28 | return true 29 | 30 | } 31 | 32 | func showNetworkActivityIndicator() { 33 | DispatchQueue.main.async { 34 | (UIApplication.shared.delegate as? AppDelegate)?.networkCounter += 1 35 | } 36 | } 37 | 38 | func hideNetworkActivityIndicator() { 39 | DispatchQueue.main.async { 40 | (UIApplication.shared.delegate as? AppDelegate)?.networkCounter -= 1 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Podverse/WebKitViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebKitViewController.swift 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 4/28/18. 6 | // Copyright © 2018 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | 12 | class WebKitViewController: UIViewController, WKUIDelegate, WKNavigationDelegate { 13 | 14 | var urlString: String? 15 | 16 | @IBOutlet weak var webView: WKWebView! 17 | @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView! 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | self.webView.uiDelegate = self 23 | 24 | self.webView.navigationDelegate = self 25 | 26 | self.activityIndicatorView.hidesWhenStopped = true 27 | 28 | if let urlString = urlString, let url = URL(string: urlString) { 29 | showNetworkActivityIndicator() 30 | self.activityIndicatorView.startAnimating() 31 | let request = URLRequest(url: url) 32 | webView.load(request) 33 | } 34 | } 35 | 36 | // func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { 37 | // showNetworkActivityIndicator() 38 | // self.activityIndicatorView.startAnimating() 39 | // } 40 | 41 | 42 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 43 | hideNetworkActivityIndicator() 44 | self.activityIndicatorView.stopAnimating() 45 | } 46 | 47 | func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { 48 | hideNetworkActivityIndicator() 49 | self.activityIndicatorView.stopAnimating() 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /Podverse/podverse-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // podverse-Bridging-Header.h 3 | // Podverse 4 | // 5 | // Created by Mitchell Downey on 8/16/17. 6 | // Copyright © 2017 Podverse LLC. All rights reserved. 7 | // 8 | 9 | #ifndef podverse_Bridging_Header_h 10 | 11 | #import "StreamingKit.h" 12 | 13 | #define podverse_Bridging_Header_h 14 | 15 | 16 | #endif /* podverse_Bridging_Header_h */ 17 | -------------------------------------------------------------------------------- /Podverse/podverse.xcdatamodeld/podverse.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /PodverseTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PodverseTests/PodverseTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PodverseTests.swift 3 | // PodverseTests 4 | // 5 | // Created by Creon on 12/15/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class PodverseTests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | super.tearDown() 21 | } 22 | 23 | func testExample() { 24 | // This is an example of a functional test case. 25 | // Use XCTAssert and related functions to verify your tests produce the correct results. 26 | let myPodcastHasbeenPlayed = false; 27 | 28 | XCTAssertTrue(myPodcastHasbeenPlayed, "Podcast has not been played. Did you hit play?") 29 | } 30 | 31 | func testPerformanceExample() { 32 | // This is an example of a performance test case. 33 | self.measure { 34 | // Put the code you want to measure the time of here. 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /PodverseUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PodverseUITests/PodverseUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PodverseUITests.swift 3 | // PodverseUITests 4 | // 5 | // Created by Creon on 12/15/16. 6 | // Copyright © 2016 Podverse LLC. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class PodverseUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # podverse-ios 2 | Podcast subscribing and clip sharing mobile app. 3 | 4 | ## Setup 5 | 6 | ### CocoaPods 7 | 8 | Dependencies are loaded using CocoaPods. After cloning the repo, you'll need to install dependencies by typing: 9 | 10 | `pod install` 11 | 12 | After install finishes, open the Podverse.xcworkspace in Xcode. 13 | 14 | ### Auth0 15 | 16 | Podverse uses Auth0 for user authentication. In order to run the app locally, you will need to sign up for an Auth0 account, then add to the Supporting Files directory a file named Auth0.plist, and add the following key/value pairs: 17 | 18 | ``` 19 | ClientId: {YOUR_CLIENT_ID} 20 | Domain: {YOUR_DOMAIN} 21 | ``` 22 | 23 | You should now be able to run the app, and login with your Auth0 app credentials. 24 | 25 | ### Server Data 26 | 27 | By default, clip and podcast search data is provided by podverse.fm. To change the source of this data, update the corresponding URL/s in the Constants.swift file: 28 | 29 | ``` 30 | let LOCAL_DEV_URL = "http://localhost:8080/" 31 | let DEV_URL = "" 32 | let PROD_URL = "" 33 | let BASE_URL = PROD_URL 34 | ``` --------------------------------------------------------------------------------