-
Notifications
You must be signed in to change notification settings - Fork 338
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
[Paywalls V2] Prefetch low res images #4658
Changes from all commits
1ef88c0
37f2b8e
fbab4b5
2c6462e
30ba5cd
ee492f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// | ||
// Copyright RevenueCat Inc. All Rights Reserved. | ||
// | ||
// Licensed under the MIT License (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// https://opensource.org/licenses/MIT | ||
// | ||
// PaywallV2CacheWarming.swift | ||
// | ||
// Created by Josh Holtz on 1/13/25. | ||
|
||
import Foundation | ||
|
||
#if PAYWALL_COMPONENTS | ||
|
||
extension PaywallComponentsData { | ||
|
||
var allImageURLs: [URL] { | ||
var imageUrls = self.componentsConfig.base.allImageURLs | ||
|
||
for (_, localeValues) in self.componentsLocalizations { | ||
for (_, value) in localeValues { | ||
switch value { | ||
case .string: | ||
break | ||
case .image(let image): | ||
imageUrls += image.imageUrls | ||
} | ||
} | ||
} | ||
|
||
return imageUrls | ||
} | ||
|
||
} | ||
|
||
extension PaywallComponentsData.PaywallComponentsConfig { | ||
|
||
var allImageURLs: [URL] { | ||
let rootStackImageURLs = self.collectAllImageURLs(in: self.stack) | ||
let stickFooterImageURLs = self.stickyFooter.flatMap { self.collectAllImageURLs(in: $0.stack) } ?? [] | ||
|
||
return rootStackImageURLs + stickFooterImageURLs | ||
} | ||
|
||
// swiftlint:disable:next cyclomatic_complexity | ||
private func collectAllImageURLs(in stack: PaywallComponent.StackComponent) -> [URL] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Traversing the tree of components is not the most performant, especially since we're already doing that to build the paywall. But I guess we need to do it in a separate traversal, because if we do it at the same time as building the paywall it's too late? (It wouldn't be prefetch anymore ha.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeaahhhh, I figured this could be improved in the future at some point if we wanted 🤷♂️ The thing this does differently than paywalls is that it doesn't load the entire view models and very localization stuff. Its pretty specific in how/what its traversing. But tbh... precaching a bunch of images is LESS performant than traversing this free but its leading to better performance of the paywall 😛 🥴 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's true, good perspective! |
||
|
||
var urls: [URL] = [] | ||
for component in stack.components { | ||
switch component { | ||
case .text: | ||
() | ||
case .icon(let icon): | ||
guard let baseUrl = URL(string: icon.baseUrl) else { | ||
break | ||
} | ||
|
||
urls += icon.formats.imageUrls(base: baseUrl) | ||
urls += icon.overrides?.imageUrls(base: baseUrl) ?? [] | ||
case .image(let image): | ||
urls += [ | ||
image.source.light.heicLowRes, | ||
image.source.dark?.heicLowRes | ||
].compactMap { $0 } | ||
|
||
if let overides = image.overrides { | ||
urls += [ | ||
overides.introOffer?.source?.imageUrls ?? [], | ||
overides.states?.selected?.source?.imageUrls ?? [], | ||
overides.conditions?.compact?.source?.imageUrls ?? [], | ||
overides.conditions?.medium?.source?.imageUrls ?? [], | ||
overides.conditions?.expanded?.source?.imageUrls ?? [] | ||
].flatMap { $0 } | ||
} | ||
case .stack(let stack): | ||
urls += self.collectAllImageURLs(in: stack) | ||
case .button(let button): | ||
urls += self.collectAllImageURLs(in: button.stack) | ||
case .package(let package): | ||
urls += self.collectAllImageURLs(in: package.stack) | ||
case .purchaseButton(let purchaseButton): | ||
urls += self.collectAllImageURLs(in: purchaseButton.stack) | ||
case .stickyFooter(let stickyFooter): | ||
urls += self.collectAllImageURLs(in: stickyFooter.stack) | ||
case .tabs(let tabs): | ||
for tab in tabs.tabs { | ||
urls += self.collectAllImageURLs(in: tab.stack) | ||
} | ||
case .tabControl: | ||
break | ||
case .tabControlButton(let controlButton): | ||
urls += self.collectAllImageURLs(in: controlButton.stack) | ||
case .tabControlToggle: | ||
break | ||
} | ||
} | ||
|
||
return urls | ||
} | ||
|
||
} | ||
|
||
extension PaywallComponent.IconComponent.Formats { | ||
|
||
func imageUrls(base: URL) -> [URL] { | ||
return [ | ||
base.appendingPathComponent(heic) | ||
] | ||
} | ||
|
||
} | ||
|
||
extension PaywallComponent.ComponentOverrides where T == PaywallComponent.PartialIconComponent { | ||
|
||
func imageUrls(base: URL) -> [URL] { | ||
return [ | ||
self.introOffer?.formats?.imageUrls(base: base) ?? [], | ||
self.states?.selected?.formats?.imageUrls(base: base) ?? [], | ||
self.conditions?.compact?.formats?.imageUrls(base: base) ?? [], | ||
self.conditions?.medium?.formats?.imageUrls(base: base) ?? [], | ||
self.conditions?.expanded?.formats?.imageUrls(base: base) ?? [] | ||
].flatMap { $0 } | ||
} | ||
|
||
} | ||
|
||
private extension PaywallComponent.ThemeImageUrls { | ||
|
||
var imageUrls: [URL] { | ||
return [ | ||
self.light.heicLowRes, | ||
self.dark?.heicLowRes | ||
].compactMap { $0 } | ||
} | ||
|
||
} | ||
|
||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This made me think: should all images be in
componentsLocalizations
, including the "base" images?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a reason against putting the base image in localization at one point and it made sense so I'm going to say... no. But now I need to remember that good argument that I had 😛
But TBH, I did think the same thing as you while writing this code how that would be easier but... we'd have to do the same thing for icons anyway 🤷♂️