Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Hack Week] Serial show support #2502

Open
wants to merge 7 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,10 @@ public enum PodcastEpisodeSortOrder: Int32, Codable, CaseIterable {
case newestToOldest
case shortestToLongest
case longestToShortest
case serial

public enum Old: Int32 {
case newestToOldest = 1, oldestToNewest, shortestToLongest, longestToShortest, titleAtoZ, titleZtoA
case newestToOldest = 1, oldestToNewest, shortestToLongest, longestToShortest, titleAtoZ, titleZtoA, serial
}

public init(old: Old) {
Expand All @@ -117,6 +118,8 @@ public enum PodcastEpisodeSortOrder: Int32, Codable, CaseIterable {
self = .titleAtoZ
case .titleZtoA:
self = .titleZtoA
case .serial:
self = .serial
}
}

Expand All @@ -134,6 +137,8 @@ public enum PodcastEpisodeSortOrder: Int32, Codable, CaseIterable {
.titleAtoZ
case .titleZtoA:
.titleZtoA
case .serial:
.serial
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ public class ServerPodcastManager: NSObject {
}
if let showType = podcastJson["show_type"] as? String {
podcast.showType = showType
if showType == "serial" {
podcast.episodeGrouping = PodcastGrouping.season.rawValue
podcast.episodeSortOrder = PodcastEpisodeSortOrder.Old.serial.rawValue
}
}
if let estimatedNextEpisode = podcastInfo["estimated_next_episode_at"] as? String {
podcast.estimatedNextEpisode = isoFormatter.date(from: estimatedNextEpisode)
Expand Down
2 changes: 2 additions & 0 deletions Pocket Casts Watch App/PodcastEpisodeListViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class PodcastEpisodeListViewModel: ObservableObject {
sortStr = "ORDER BY duration ASC, addedDate"
case .longestToShortest:
sortStr = "ORDER BY duration DESC, addedDate"
case .serial:
sortStr = "ORDER BY CASE WHEN seasonNumber < 1 THEN 9999 ELSE seasonNumber END, CASE WHEN episodeNumber < 1 THEN 9999 ELSE episodeNumber END ASC, publishedDate ASC"
}

return "podcast_id = \(podcast.id) AND archived = 0 \(sortStr) LIMIT \(Constants.Limits.watchListItems)"
Expand Down
4 changes: 4 additions & 0 deletions podcasts/Enumerations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ extension PodcastEpisodeSortOrder: AnalyticsDescribable {
return L10n.podcastsEpisodeSortShortestToLongest
case .longestToShortest:
return L10n.podcastsEpisodeSortLongestToShortest
case .serial:
return L10n.podcastsEpisodeSortSerial
}
}

Expand All @@ -117,6 +119,8 @@ extension PodcastEpisodeSortOrder: AnalyticsDescribable {
return "shortest_to_longest"
case .longestToShortest:
return "longest_to_shortest"
case .serial:
return "serial"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion podcasts/EpisodeTableHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ struct EpisodeTableHelper {

var newData = ArraySection<String, ListItem>(model: "episodes", elements: [])
for section in sortedSections {
newData.elements.append(ListHeader(headerTitle: section.key, isSectionHeader: false))
newData.elements.append(ListHeader(headerTitle: section.key, isSectionHeader: false, sectionNumber: Int(section.value.first?.episode.seasonNumber ?? -1)))
newData.elements.append(contentsOf: section.value)
}

Expand Down
18 changes: 15 additions & 3 deletions podcasts/EpisodesDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class EpisodesDataManager {
/// Use `uuidsToFilter` to filter the episode UUIDs to only those in the array
func episodes(for podcast: Podcast, uuidsToFilter: [String]? = nil) -> [ArraySection<String, ListItem>] {
// the podcast page has a header, for simplicity in table animations, we add it here
let searchHeader = ListHeader(headerTitle: L10n.search, isSectionHeader: true)
let searchHeader = ListHeader(headerTitle: L10n.search, isSectionHeader: true, sectionNumber: -1)
var newData = [ArraySection<String, ListItem>(model: searchHeader.headerTitle, elements: [searchHeader])]

let episodeSortOrder = podcast.podcastSortOrder
Expand All @@ -45,9 +45,19 @@ class EpisodesDataManager {
newData.append(ArraySection(model: "episodes", elements: episodes))
case .season:
let groupedEpisodes = EpisodeTableHelper.loadSortedSectionedEpisodes(query: createEpisodesQuery(podcast, uuidsToFilter: uuidsToFilter), arguments: nil, sectionComparator: { name1, name2 -> Bool in
sortOrder == .newestToOldest ? name1.digits > name2.digits : name2.digits > name1.digits
if sortOrder == .serial {
if name2 == L10n.podcastNoSeason {
return true
} else if name1 == L10n.podcastNoSeason {
return false
} else {
return name2.digits > name1.digits
}
} else {
return sortOrder == .newestToOldest ? name1.digits > name2.digits : name2.digits > name1.digits
}
}, episodeShortKey: { episode -> String in
episode.seasonNumber > 0 ? L10n.podcastSeasonFormat(episode.seasonNumber.localized()) : L10n.podcastNoSeason
episode.seasonNumber > 0 ? L10n.podcastSeasonFormat(episode.seasonNumber.localized()) : (sortOrder == .serial ? L10n.podcastExtras : L10n.podcastNoSeason)
})
newData.append(contentsOf: groupedEpisodes)
case .unplayed:
Expand Down Expand Up @@ -109,6 +119,8 @@ class EpisodesDataManager {
sortStr = "ORDER BY duration ASC, addedDate"
case .longestToShortest:
sortStr = "ORDER BY duration DESC, addedDate"
case .serial:
sortStr = "ORDER BY CASE WHEN seasonNumber < 1 THEN 9999 ELSE seasonNumber END, CASE WHEN episodeNumber < 1 THEN 9999 ELSE episodeNumber END ASC, publishedDate ASC"
}
if let uuids = uuidsToFilter {
let inClause = "(\(uuids.map { "'\($0)'" }.joined(separator: ",")))"
Expand Down
7 changes: 7 additions & 0 deletions podcasts/HeadingCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import UIKit

class HeadingCell: ThemeableCell {
@IBOutlet var heading: UILabel!
@IBOutlet var button: UIButton!

var action: (() -> ())?

override func setSelected(_ selected: Bool, animated: Bool) {}
override func setHighlighted(_ highlighted: Bool, animated: Bool) {}

@IBAction func buttonTapped(_ sender: UIButton) {
action?()
}
}
58 changes: 44 additions & 14 deletions podcasts/HeadingCell.xib
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
Expand All @@ -16,26 +15,57 @@
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aKG-Ba-bwG" customClass="ThemeableLabel" customModule="podcasts" customModuleProvider="target">
<rect key="frame" x="16" y="8.5" width="57" height="27"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="22"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="gur-cm-zgD">
<rect key="frame" x="16" y="0.0" width="296" height="44"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aKG-Ba-bwG" customClass="ThemeableLabel" customModule="podcasts" customModuleProvider="target">
<rect key="frame" x="0.0" y="9" width="56" height="26.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="22"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="t6Q-Yd-I4z">
<rect key="frame" x="56" y="0.0" width="196" height="44"/>
<viewLayoutGuide key="safeArea" id="ov3-sd-w2F"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VrO-EU-lY2">
<rect key="frame" x="252" y="0.0" width="44" height="44"/>
<constraints>
<constraint firstAttribute="width" constant="44" id="myS-Wq-bQU"/>
<constraint firstAttribute="height" constant="44" id="s8D-dh-IJF"/>
</constraints>
<state key="normal" title="Button"/>
<buttonConfiguration key="configuration" style="plain" image="more" imagePadding="0.0"/>
<connections>
<action selector="buttonTapped:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="dRr-3K-rOv"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
<constraints>
<constraint firstItem="aKG-Ba-bwG" firstAttribute="centerY" secondItem="H2p-sc-9uM" secondAttribute="centerY" id="QHv-sd-ui6"/>
<constraint firstItem="aKG-Ba-bwG" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="16" id="a2S-k8-Rfo"/>
<constraint firstAttribute="bottom" secondItem="gur-cm-zgD" secondAttribute="bottom" id="1tr-kq-y71"/>
<constraint firstAttribute="trailing" secondItem="gur-cm-zgD" secondAttribute="trailing" constant="8" id="L8e-lT-eI4"/>
<constraint firstItem="gur-cm-zgD" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="16" id="Lqn-V9-hPX"/>
<constraint firstItem="gur-cm-zgD" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="pUK-Gw-xtR"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<connections>
<outlet property="button" destination="VrO-EU-lY2" id="zSO-Y8-bxi"/>
<outlet property="heading" destination="aKG-Ba-bwG" id="vgM-rp-84Y"/>
</connections>
<point key="canvasLocation" x="-368" y="36"/>
</tableViewCell>
</objects>
<resources>
<image name="more" width="24" height="24"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
5 changes: 3 additions & 2 deletions podcasts/ListHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import Foundation
class ListHeader: ListItem {
let headerTitle: String
let isSectionHeader: Bool
let sectionNumber: Int

init(headerTitle: String, isSectionHeader: Bool) {
init(headerTitle: String, isSectionHeader: Bool, sectionNumber: Int) {
self.headerTitle = headerTitle
self.isSectionHeader = isSectionHeader

self.sectionNumber = sectionNumber
super.init()
}

Expand Down
9 changes: 9 additions & 0 deletions podcasts/PodcastViewController+TableData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ extension PodcastViewController: UITableViewDataSource, UITableViewDelegate {
} else if let heading = itemAtRow as? ListHeader {
let cell = tableView.dequeueReusableCell(withIdentifier: PodcastViewController.groupHeadingCellId, for: indexPath) as! HeadingCell
cell.heading.text = heading.headerTitle
if heading.sectionNumber > 0 {
cell.button.isHidden = false
cell.action = { [weak self] in
self?.showOptionsFor(season: heading.sectionNumber)
}
} else {
cell.button.isHidden = true
cell.action = nil
}
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: PodcastViewController.limitCellId, for: indexPath) as! EpisodeLimitCell
Expand Down
43 changes: 43 additions & 0 deletions podcasts/PodcastViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,49 @@ class PodcastViewController: FakeNavViewController, PodcastActionsDelegate, Sync
}
}

func showOptionsFor(season: Int) {
let optionPicker = OptionsPicker(title: nil)

optionPicker.addActions([
.init(label: L10n.selectAll, icon: "selectall-down") { [weak self] in

},
.init(label: L10n.downloadAll, icon: "player-download") { [weak self] in
self?.downloadSeasonTapped(season: season)
},
.init(label: L10n.podcastArchiveAll, icon: "options-archiveall") { [weak self] in

}
])

optionPicker.show(statusBarStyle: AppTheme.defaultStatusBarStyle())
}

func downloadSeasonTapped(season: Int) {
DispatchQueue.global().async { [weak self] in
guard let self = self,
let allObjects = self.episodeInfo[safe: 1]?.elements,
allObjects.count > 0
else {
return
}

let seasonObjects = allObjects.filter {
guard let listEpisode = $0 as? ListEpisode else {
return false
}
return listEpisode.episode.seasonNumber == season
}

let episodes = seasonObjects.compactMap { ($0 as? ListEpisode)?.episode }.filter { $0.seasonNumber == season }

AnalyticsEpisodeHelper.shared.currentSource = .podcastScreen
AnalyticsEpisodeHelper.shared.bulkDownloadEpisodes(episodes: episodes)

self.downloadItems(allObjects: seasonObjects)
}
}

func downloadItems(allObjects: [ListItem]) {
var queuedEpisodes = 0
for object in allObjects {
Expand Down
6 changes: 5 additions & 1 deletion podcasts/Strings+Generated.swift

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion podcasts/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1852,7 +1852,7 @@
"podcast_no_date" = "Date Not Set";

/* Label used to indicate that the podcast episode isn't grouped into a season. */
"podcast_no_season" = "No Season";
"podcast_no_season" = "Extras";

/* Accessibility label to prompt to pause an active download. */
"podcast_pause_download" = "Pause download";
Expand Down Expand Up @@ -4722,3 +4722,10 @@

/* Message when no email account is configured to be able to send the logs*/
"logs_no_email_account_configured" = "You need to configure an email account on the device in order to send the logs";
/* Episodes will be displayed in based on season and episode numbers*/
"podcasts_episode_sort_serial" = "Serial";

/* Name for group of episodes that don't have a season defined when sorting in serial mode*/
"podcast_extras" = "Extras";


Loading