Skip to content

Commit

Permalink
Playback 2024: Longest Episode (#2347)
Browse files Browse the repository at this point in the history
  • Loading branch information
bjtitus authored Oct 29, 2024
2 parents e21acbe + db6f280 commit d45bc0e
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 29 deletions.
4 changes: 4 additions & 0 deletions podcasts.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1721,6 +1721,7 @@
F5F6DA702BBE1109009B1934 /* CategoryButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F6DA6F2BBE1109009B1934 /* CategoryButtonStyle.swift */; };
F5F6DA822BC0B512009B1934 /* CategoriesModalPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F6DA812BC0B512009B1934 /* CategoriesModalPicker.swift */; };
F5F884632CC9EAA6002BED2C /* Humane-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = F5F884622CC9EAA6002BED2C /* Humane-Bold.otf */; };
F5F884652CCA86AE002BED2C /* LongestEpisode2024Story.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F884642CCA86AA002BED2C /* LongestEpisode2024Story.swift */; };
F5F89B1E2C88B40A00013118 /* ShareButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F89B1D2C88B40A00013118 /* ShareButton.swift */; };
F5F8F3182CC314250071DD0E /* IntroStory2024.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F8F3172CC314250071DD0E /* IntroStory2024.swift */; };
F5FE747B2C223A6100DF2EAA /* ShareImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5FF61222BD07E3A00190711 /* ShareImageView.swift */; };
Expand Down Expand Up @@ -3659,6 +3660,7 @@
F5F6DA6F2BBE1109009B1934 /* CategoryButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryButtonStyle.swift; sourceTree = "<group>"; };
F5F6DA812BC0B512009B1934 /* CategoriesModalPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoriesModalPicker.swift; sourceTree = "<group>"; };
F5F884622CC9EAA6002BED2C /* Humane-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Humane-Bold.otf"; sourceTree = "<group>"; };
F5F884642CCA86AA002BED2C /* LongestEpisode2024Story.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongestEpisode2024Story.swift; sourceTree = "<group>"; };
F5F89B1D2C88B40A00013118 /* ShareButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareButton.swift; sourceTree = "<group>"; };
F5F8F3172CC314250071DD0E /* IntroStory2024.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroStory2024.swift; sourceTree = "<group>"; };
F5FF611F2BD076BA00190711 /* Sharing.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Sharing.xcassets; sourceTree = "<group>"; };
Expand Down Expand Up @@ -7874,6 +7876,7 @@
F53C3E822CC73538004F3581 /* TopSpotStory2024.swift */,
F581ED412CC6F19300A19860 /* ListeningTime2024Story.swift */,
F5D527372CC81AA700682CD5 /* EpilogueStory2024.swift */,
F5F884642CCA86AA002BED2C /* LongestEpisode2024Story.swift */,
F581ED432CC6F3A400A19860 /* Top5Podcasts2024Story.swift */,
);
path = 2024;
Expand Down Expand Up @@ -9491,6 +9494,7 @@
C7FAFF5D2941844C00329B40 /* CancelConfirmationViewModel.swift in Sources */,
BD14CCDF1D7D3CB800DB4547 /* SelectedPodcastCell.swift in Sources */,
BD93FDA120157B2000F6EF55 /* PodcastImageView.swift in Sources */,
F5F884652CCA86AE002BED2C /* LongestEpisode2024Story.swift in Sources */,
BDD5253A20477E4400AAD211 /* NSObject+AppDelegate.swift in Sources */,
8B14E3B029B9159B0069B6F2 /* SearchHistoryModel.swift in Sources */,
C7080C5D2923070200D7A432 /* PlusAccountUpgradePrompt.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ class EndOfYear2024StoriesModel: StoryModel {
data.listeningTime = listeningTime
}

// Longest episode
if let longestEpisode = dataManager.longestEpisode(in: Self.year),
let podcast = longestEpisode.parentPodcast() {
data.longestEpisode = longestEpisode
data.longestEpisodePodcast = podcast
stories.append(.longestEpisode)
}
}

func story(for storyNumber: Int) -> any StoryView {
Expand All @@ -34,6 +41,8 @@ class EndOfYear2024StoriesModel: StoryModel {
return Top5Podcasts2024Story(top5Podcasts: data.topPodcasts)
case .listeningTime:
return ListeningTime2024Story(listeningTime: data.listeningTime)
case .longestEpisode:
return LongestEpisode2024Story(episode: data.longestEpisode, podcast: data.longestEpisodePodcast)
case .epilogue:
return EpilogueStory2024()
}
Expand Down Expand Up @@ -91,4 +100,9 @@ class EndOfYear2024StoriesData {
var topPodcasts: [TopPodcast] = []

var listeningTime: Double = 0

var longestEpisode: Episode!

var longestEpisodePodcast: Podcast!

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ enum EndOfYear2024Story: CaseIterable {
case intro
case top5Podcasts
case listeningTime
case longestEpisode
case epilogue
}
132 changes: 132 additions & 0 deletions podcasts/End of Year/Stories/2024/LongestEpisode2024Story.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import SwiftUI
import PocketCastsDataModel

struct LongestEpisode2024Story: ShareableStory {
@Environment(\.renderForSharing) var renderForSharing: Bool
@Environment(\.animated) var animated: Bool

@ObservedObject private var animationViewModel = PlayPauseAnimationViewModel(duration: 0.8, animation: Animation.spring(_:))

var identifier: String = "longest_episode"

let episode: Episode

let podcast: Podcast

@State var firstCover: Double = 0.4
@State var secondCover: Double = 0.32
@State var thirdCover: Double = 0.24
@State var fourthCover: Double = 0.16
@State var fifthCover: Double = 0.08
@State var sixthCover: Double = 0

private let backgroundColor = Color(hex: "#E0EFAD")
private let foregroundColor = Color.black

var body: some View {
GeometryReader { geometry in
let isSmallScreen = geometry.size.height <= 600
VStack(alignment: .leading) {
Spacer()
ZStack {
covers()
let stickerSize = CGSize(width: 194, height: 135)
Image("playback-sticker-phew")
.resizable()
.frame(width: stickerSize.width, height: stickerSize.height)
.position(x: -6, y: 0, for: stickerSize, in: geometry.frame(in: .global), corner: .topTrailing)
}
.frame(width: geometry.size.width * 0.9)
.padding(.top, isSmallScreen ? 0 : 20)
VStack(alignment: .leading, spacing: isSmallScreen ? 4 : 16) {
let timeString = episode.playedUpTo.storyTimeDescriptionForSharing
Text(L10n.playback2024LongestEpisodeTitle(timeString))
.font(.system(size: 31, weight: .bold))
Text(L10n.playback2024LongestEpisodeDescription(episode.title ?? "unknown", podcast.title ?? "unknown"))
.font(.system(size: 15, weight: .light))
}
.padding(.horizontal, 24)
.padding(.bottom, isSmallScreen ? 4 : 16)
}
}
.background(backgroundColor)
.foregroundStyle(foregroundColor)
.onAppear {
if animated {
setInitialCoverOffsetForAnimation()
animationViewModel.play()
}
}
}

@ViewBuilder func covers() -> some View {
GeometryReader { geometry in
PodcastCoverContainer(geometry: geometry) {
ZStack {
PodcastCover(podcastUuid: podcast.uuid)
.frame(width: geometry.size.width * 0.5, height: geometry.size.width * 0.5)
.offset(x: -geometry.size.width * firstCover, y: geometry.size.width * firstCover)
.modifier(animationViewModel.animate($firstCover, to: 0.4))

PodcastCover(podcastUuid: podcast.uuid)
.frame(width: geometry.size.width * 0.55, height: geometry.size.width * 0.55)
.offset(x: -geometry.size.width * secondCover, y: geometry.size.width * secondCover)
.modifier(animationViewModel.animate($secondCover, to: 0.32))

PodcastCover(podcastUuid: podcast.uuid)
.frame(width: geometry.size.width * 0.6, height: geometry.size.width * 0.6)
.offset(x: -geometry.size.width * thirdCover, y: geometry.size.width * thirdCover)
.modifier(animationViewModel.animate($thirdCover, to: 0.24))

PodcastCover(podcastUuid: podcast.uuid)
.frame(width: geometry.size.width * 0.65, height: geometry.size.width * 0.65)
.offset(x: -geometry.size.width * fourthCover, y: geometry.size.width * fourthCover)
.modifier(animationViewModel.animate($fourthCover, to: 0.16))

PodcastCover(podcastUuid: podcast.uuid)
.frame(width: geometry.size.width * 0.7, height: geometry.size.width * 0.7)
.offset(x: -geometry.size.width * fifthCover, y: geometry.size.width * fifthCover)
.modifier(animationViewModel.animate($fifthCover, to: 0.08))

PodcastCover(podcastUuid: podcast.uuid, higherQuality: true)
.frame(width: geometry.size.width * 0.75, height: geometry.size.width * 0.75)
.offset(x: -geometry.size.width * sixthCover, y: geometry.size.width * sixthCover)
.modifier(animationViewModel.animate($sixthCover, to: 0))
}
.offset(x: geometry.size.width * 0.04, y: geometry.size.height * 0.09)
}
}
}

func onAppear() {
Analytics.track(.endOfYearStoryShown, story: identifier)
}

func onPause() {
animationViewModel.pause()
}

func onResume() {
animationViewModel.play()
}

func willShare() {
Analytics.track(.endOfYearStoryShare, story: identifier)
}

func sharingAssets() -> [Any] {
[
StoryShareableProvider.new(AnyView(self)),
StoryShareableText(L10n.eoyStoryLongestEpisodeShareText("%1$@"), episode: episode)
]
}

private func setInitialCoverOffsetForAnimation() {
firstCover = 0.8
secondCover = 0.8
thirdCover = 0.8
fourthCover = 0.8
fifthCover = 0.8
sixthCover = 0.8
}
}
29 changes: 1 addition & 28 deletions podcasts/End of Year/Stories/Views/PodcastCover.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ struct PodcastCover: View {
/// If the artwork needs a bigger image with higher quality
var higherQuality: Bool = false

@State private var image: UIImage?
@Environment(\.renderForSharing) var renderForSharing: Bool

private var rectangleColor: Color? {
Expand All @@ -40,36 +39,10 @@ struct PodcastCover: View {
.modifier(NormalCoverShadow())
}
}
.opacity(image != nil ? 1 : 0.2)
.blendMode(.multiply)

ImageView(image: image)
PodcastImage(uuid: podcastUuid, size: .page)
.cornerRadius(big ? 8 : 4)

.onAppear {
if renderForSharing {
loadImage()
}
}

Action {
if !renderForSharing {
loadImage()
}
}
}
}

private func loadImage() {
image = nil
let size = higherQuality ? 680 : 280
KingfisherManager.shared.retrieveImage(with: ServerHelper.imageUrl(podcastUuid: podcastUuid, size: size)) { result in
switch result {
case .success(let result):
image = result.image
default:
break
}
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion podcasts/End of Year/Views/StoriesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ struct StoriesView: View {
// Manually set the zIndex order to ensure we can change the order when needed
model.story(index: model.currentStoryIndex)
.zIndex(3)
.ignoresSafeArea(edges: .bottom)
.modify {
if model.overlaidShareView() != nil {
$0.ignoresSafeArea(edges: .bottom)
}
}
.environment(\.animated, true)

if model.shouldShowUpsell() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "playback-sticker-phew.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions podcasts/Strings+Generated.swift

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

6 changes: 6 additions & 0 deletions podcasts/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -3557,6 +3557,12 @@
/* A description used to indicate the number of days and hours spent listening to podcasts in the last year */
"playback_2024_listening_time_description" = "%1$@ total listening to podcasts";

/* A title shown with the amount of time listened on the Longest Episode screen of Playback 2024 */
"playback_2024_longest_episode_title" = "The longest episode you listened to was %1$@";

/* A description shown on the Longest Episode screen of Playback 2024 with the episode title and podcast title */
"playback_2024_longest_episode_description" = "It was \"%1$@\" from \"%2$@\"";

/* Label of the End of Year dismiss button */
"eoy_not_now" = "Not Now";

Expand Down

0 comments on commit d45bc0e

Please sign in to comment.