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

End of Year: preload stories #471

Merged
merged 10 commits into from
Nov 2, 2022
4 changes: 4 additions & 0 deletions podcasts.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@
8BB1187F290C56F7009E3A39 /* CategoryPillar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB1187E290C56F7009E3A39 /* CategoryPillar.swift */; };
8BB11881290C5720009E3A39 /* CategoriesContrastingColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB11880290C5720009E3A39 /* CategoriesContrastingColor.swift */; };
8BB55E3A28FEEE99001D1766 /* StoryShareableProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB55E3928FEEE99001D1766 /* StoryShareableProvider.swift */; };
8BC7FF55290FF92400017779 /* StoriesProgressModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BC7FF54290FF92400017779 /* StoriesProgressModel.swift */; };
8BCB22B228F47F44001A0315 /* EndOfYearModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BCB22B128F47F44001A0315 /* EndOfYearModal.swift */; };
8BCB22B428F48282001A0315 /* EndOfYear.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BCB22B328F48282001A0315 /* EndOfYear.swift */; };
8BDEBC0F28F6F82A00A1B03C /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 8BDEBC0E28F6F82A00A1B03C /* [email protected] */; };
Expand Down Expand Up @@ -2056,6 +2057,7 @@
8BB1187E290C56F7009E3A39 /* CategoryPillar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPillar.swift; sourceTree = "<group>"; };
8BB11880290C5720009E3A39 /* CategoriesContrastingColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoriesContrastingColor.swift; sourceTree = "<group>"; };
8BB55E3928FEEE99001D1766 /* StoryShareableProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryShareableProvider.swift; sourceTree = "<group>"; };
8BC7FF54290FF92400017779 /* StoriesProgressModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoriesProgressModel.swift; sourceTree = "<group>"; };
8BCB22B128F47F44001A0315 /* EndOfYearModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndOfYearModal.swift; sourceTree = "<group>"; };
8BCB22B328F48282001A0315 /* EndOfYear.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndOfYear.swift; sourceTree = "<group>"; };
8BDEBC0E28F6F82A00A1B03C /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3771,6 +3773,7 @@
8B6B68E428F744520032BFFF /* StoriesDataSource.swift */,
8B5AB48D2901A8BD0018C637 /* StoriesController.swift */,
8B6B68E628F74A010032BFFF /* StoriesModel.swift */,
8BC7FF54290FF92400017779 /* StoriesProgressModel.swift */,
8BB55E3928FEEE99001D1766 /* StoryShareableProvider.swift */,
);
path = "End of Year";
Expand Down Expand Up @@ -7326,6 +7329,7 @@
8B738F3128F5CBE0004E7526 /* StoriesView.swift in Sources */,
BDDA14DC219B021A0066441E /* SearchLoadingCell.swift in Sources */,
BD16385A27B0FC5A00F24A39 /* PodcastPickerView.swift in Sources */,
8BC7FF55290FF92400017779 /* StoriesProgressModel.swift in Sources */,
408B932C216EFD9B00B57B78 /* TextEntryCell.swift in Sources */,
BDF9DDD91B8AD29C00E77B35 /* TimeSlider.swift in Sources */,
403B5AFE2179670F00821A54 /* MediaFilterOverlayController.swift in Sources */,
Expand Down
65 changes: 56 additions & 9 deletions podcasts/End of Year/Stories/DynamicBackgroundView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,19 @@ import SwiftUI
import PocketCastsDataModel

struct DynamicBackgroundView: View {
let backgroundColor: Color

let foregroundColor: Color
@ObservedObject private var model: DynamicBackgroundProvider

init(podcast: Podcast) {
backgroundColor = podcast.bgColor().color
foregroundColor = ColorManager.lightThemeTintForPodcast(podcast).color
model = DynamicBackgroundProvider(podcast: podcast)
}

init(backgroundColor: Color, foregroundColor: Color) {
self.backgroundColor = backgroundColor
self.foregroundColor = foregroundColor
model = DynamicBackgroundProvider(backgroundColor: backgroundColor, foregroundColor: foregroundColor)
}

var body: some View {
ZStack {
backgroundColor
model.backgroundColor

Color.black.opacity(0.2)

Expand All @@ -34,13 +30,64 @@ struct DynamicBackgroundView: View {
Image("bottom_blob")
.resizable()
.renderingMode(.template)
.foregroundColor(foregroundColor)
.foregroundColor(model.foregroundColor)
}

}
}
}

@MainActor
class DynamicBackgroundProvider: ObservableObject {
@Published var backgroundColor: Color
@Published var foregroundColor: Color

private let podcast: Podcast?

init(podcast: Podcast) {
emilylaguna marked this conversation as resolved.
Show resolved Hide resolved
backgroundColor = podcast.bgColor().color
foregroundColor = ColorManager.lightThemeTintForPodcast(podcast).color
self.podcast = podcast

if !ColorManager.podcastHasBackgroundColor(podcast) {
_ = ColorManager.backgroundColorForPodcast(podcast)
emilylaguna marked this conversation as resolved.
Show resolved Hide resolved
NotificationCenter.default.addObserver(self, selector: #selector(podcastColorsLoaded(_:)), name: Constants.Notifications.podcastColorsDownloaded, object: nil)
emilylaguna marked this conversation as resolved.
Show resolved Hide resolved
}
}

init(backgroundColor: Color, foregroundColor: Color) {
self.backgroundColor = backgroundColor
self.foregroundColor = foregroundColor
self.podcast = nil
}

@objc private func podcastColorsLoaded(_ notification: Notification) {
guard let uuidLoaded = notification.object as? String else { return }

if let podcast, uuidLoaded == podcast.uuid {
podcast.updateColors()
backgroundColor = podcast.bgColor().color
foregroundColor = ColorManager.lightThemeTintForPodcast(podcast).color
}
}
}

extension Podcast {
/// Update current Podcast instance with the latest downloaded colors
func updateColors() {
guard let podcast = DataManager.sharedManager.findPodcast(uuid: uuid, includeUnsubscribed: true) else {
return
}

backgroundColor = podcast.backgroundColor
primaryColor = podcast.primaryColor
secondaryColor = podcast.secondaryColor
colorVersion = podcast.colorVersion
lastColorDownloadDate = podcast.lastColorDownloadDate
}
}


struct DynamicBackgroundView_Previews: PreviewProvider {
static var previews: some View {
DynamicBackgroundView(podcast: Podcast.previewPodcast())
Expand Down
8 changes: 8 additions & 0 deletions podcasts/End of Year/StoriesConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,12 @@ class StoriesConfiguration {
///
/// Default value is `false`
var startOverFromBeginningAfterFinished: Bool = false

/// The number of stories to preload
///
/// When showing the story number zero, StoriesView will
/// try to load the next `storiesToPreload` number, so any
/// images or other assets can start loading before it's
/// actually shown.
var storiesToPreload: Int = 2
}
15 changes: 14 additions & 1 deletion podcasts/End of Year/StoriesModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SwiftUI

@MainActor
class StoriesModel: ObservableObject {
@Published var progress: Double
var progress: Double

@Published var currentStory: Int = 0

Expand All @@ -24,6 +24,10 @@ class StoriesModel: ObservableObject {
dataSource.numberOfStories
}

var numberOfStoriesToPreload: Int {
configuration.storiesToPreload
}

init(dataSource: StoriesDataSource, configuration: StoriesConfiguration) {
self.dataSource = dataSource
self.configuration = configuration
Expand Down Expand Up @@ -57,13 +61,22 @@ class StoriesModel: ObservableObject {
}

self.progress = newProgress
StoriesProgressModel.shared.progress = newProgress
})
}

func story(index: Int) -> AnyView {
dataSource.storyView(for: index)
}

func preload(index: Int) -> AnyView {
if index < numberOfStories {
return story(index: index)
}

return AnyView(EmptyView())
}

func interactive(index: Int) -> AnyView {
dataSource.interactiveView(for: index)
}
Expand Down
14 changes: 14 additions & 0 deletions podcasts/End of Year/StoriesProgressModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Combine

/// Model that publishes the progress of Stories
///
/// This is a singleton because it's shared between different views.
class StoriesProgressModel: ObservableObject {
@Published var progress: Double

static let shared = StoriesProgressModel()

private init() {
progress = 0
}
}
16 changes: 15 additions & 1 deletion podcasts/End of Year/Views/StoriesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ struct StoriesView: View {
ZStack {
Spacer()

storiesToPreload

ZStack {
model.story(index: model.currentStory)
}
Expand Down Expand Up @@ -83,7 +85,7 @@ struct StoriesView: View {
VStack {
HStack {
ForEach(0 ..< model.numberOfStories, id: \.self) { x in
StoryIndicator(progress: min(max((CGFloat(model.progress) - CGFloat(x)), 0.0), 1.0))
StoryIndicator(index: x)
}
}
.frame(height: Constants.storyIndicatorHeight)
Expand Down Expand Up @@ -177,6 +179,18 @@ struct StoriesView: View {
.padding(.leading, Constants.shareButtonHorizontalPadding)
.padding(.trailing, Constants.shareButtonHorizontalPadding)
}

var storiesToPreload: some View {
ZStack {
if model.numberOfStoriesToPreload > 0 {
ForEach(0...model.numberOfStoriesToPreload, id: \.self) { index in
model.preload(index: model.currentStory + index + 1)
}
}
}
.opacity(0)
.allowsHitTesting(false)
}
}

// MARK: - Constants
Expand Down
6 changes: 4 additions & 2 deletions podcasts/End of Year/Views/StoryIndicator.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import SwiftUI

struct StoryIndicator: View {
var progress: CGFloat
@ObservedObject private var model = StoriesProgressModel.shared

let index: Int

var body: some View {
GeometryReader { geometry in
Expand All @@ -11,7 +13,7 @@ struct StoryIndicator: View {
.cornerRadius(Constants.storyIndicatorBorderRadius)

Rectangle()
.frame(width: geometry.size.width * progress, height: nil, alignment: .leading)
.frame(width: geometry.size.width * (min(max((CGFloat(model.progress) - CGFloat(index)), 0.0), 1.0)), height: nil, alignment: .leading)
emilylaguna marked this conversation as resolved.
Show resolved Hide resolved
.foregroundColor(Color.white.opacity(Constants.storyIndicatorForegroundOpacity))
.cornerRadius(Constants.storyIndicatorBorderRadius)
}
Expand Down