-
Notifications
You must be signed in to change notification settings - Fork 316
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
: added header image to configuration
#2800
Changes from all commits
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,42 @@ | ||
// | ||
// DebugErrorView.swift | ||
// | ||
// | ||
// Created by Nacho Soto on 7/13/23. | ||
// | ||
|
||
import Foundation | ||
import SwiftUI | ||
|
||
/// A view that displays an error in debug builds | ||
@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) | ||
struct DebugErrorView: View { | ||
|
||
private let description: String | ||
|
||
init(_ error: Error) { | ||
self.init((error as NSError).localizedDescription) | ||
} | ||
|
||
init(_ description: String) { | ||
self.description = description | ||
} | ||
|
||
var body: some View { | ||
#if DEBUG | ||
Text(self.description) | ||
.background( | ||
Color.red | ||
.edgesIgnoringSafeArea(.all) | ||
) | ||
#else | ||
// Fix-me: implement a proper production error screen | ||
// appropriate for each case | ||
EmptyView() | ||
.onAppear { | ||
Logger.warning("Couldn't load paywall: \(self.description)") | ||
} | ||
#endif | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// | ||
// FitToAspectRatio.swift | ||
// | ||
// | ||
// Created by Nacho Soto on 7/13/23. | ||
// | ||
|
||
import Foundation | ||
import SwiftUI | ||
|
||
@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) | ||
struct FitToAspectRatio: ViewModifier { | ||
|
||
let aspectRatio: Double | ||
let contentMode: SwiftUI.ContentMode | ||
|
||
func body(content: Content) -> some View { | ||
Color.clear | ||
.aspectRatio(self.aspectRatio, contentMode: .fit) | ||
.overlay( | ||
content.aspectRatio(nil, contentMode: self.contentMode) | ||
) | ||
} | ||
|
||
} | ||
|
||
@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) | ||
extension Image { | ||
|
||
func fitToAspect(_ aspectRatio: Double, contentMode: SwiftUI.ContentMode) -> some View { | ||
self.resizable() | ||
.scaledToFill() | ||
.modifier(FitToAspectRatio(aspectRatio: aspectRatio, contentMode: contentMode)) | ||
} | ||
|
||
} |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,22 +9,24 @@ struct Example1Template: TemplateViewType { | |
init( | ||
packages: [Package], | ||
localization: PaywallData.LocalizedConfiguration, | ||
configuration: PaywallData.Configuration | ||
paywall: PaywallData | ||
) { | ||
// Fix-me: move this logic out to be used by all templates | ||
if packages.isEmpty { | ||
self.data = .failure(.noPackages) | ||
} else { | ||
let packages = Self.filter(packages: packages, with: configuration.packages) | ||
let allPackages = paywall.config.packages | ||
let packages = Self.filter(packages: packages, with: allPackages) | ||
|
||
if let package = packages.first { | ||
self.data = .success(.init( | ||
package: package, | ||
localization: localization.processVariables(with: package), | ||
configuration: configuration | ||
configuration: paywall.config, | ||
headerImageURL: paywall.headerImageURL | ||
)) | ||
} else { | ||
self.data = .failure(.couldNotFindAnyPackages(expectedTypes: configuration.packages)) | ||
self.data = .failure(.couldNotFindAnyPackages(expectedTypes: allPackages)) | ||
} | ||
} | ||
} | ||
|
@@ -35,19 +37,7 @@ struct Example1Template: TemplateViewType { | |
case let .success(data): | ||
Example1TemplateContent(data: data) | ||
case let .failure(error): | ||
#if DEBUG | ||
// Fix-me: implement a proper production error screen | ||
EmptyView() | ||
.onAppear { | ||
Logger.warning("Couldn't load paywall: \(error.description)") | ||
} | ||
#else | ||
Text(error.description) | ||
.background( | ||
Color.red | ||
.edgesIgnoringSafeArea(.all) | ||
) | ||
#endif | ||
DebugErrorView(error) | ||
} | ||
} | ||
|
||
|
@@ -71,17 +61,25 @@ private struct Example1TemplateContent: View { | |
@ViewBuilder | ||
private var content: some View { | ||
VStack { | ||
Image("image", bundle: .module) | ||
.resizable() | ||
.frame(maxWidth: .infinity) | ||
.aspectRatio(1, contentMode: .fill) | ||
.edgesIgnoringSafeArea(.top) | ||
.padding(.bottom) | ||
.mask(alignment: .top) { | ||
Circle() | ||
.offset(y: -160) | ||
.scale(2.5) | ||
AsyncImage(url: self.data.headerImageURL) { phase in | ||
if let image = phase.image { | ||
image | ||
.fitToAspect(Self.imageAspectRatio, contentMode: .fill) | ||
.edgesIgnoringSafeArea(.top) | ||
} else if let error = phase.error { | ||
DebugErrorView("Error loading image from '\(self.data.headerImageURL)': \(error)") | ||
} else { | ||
Rectangle() | ||
.hidden() | ||
} | ||
} | ||
.frame(maxWidth: .infinity) | ||
.aspectRatio(Self.imageAspectRatio, contentMode: .fit) | ||
.clipShape( | ||
Circle() | ||
.offset(y: -100) | ||
.scale(3.0) | ||
) | ||
|
||
Spacer() | ||
|
||
|
@@ -133,6 +131,8 @@ private struct Example1TemplateContent: View { | |
.controlSize(.large) | ||
} | ||
|
||
private static let imageAspectRatio = 0.7 | ||
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. 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. Yeah once we figure out what we want to do for the "actual" template we can revisit this. |
||
|
||
} | ||
|
||
// MARK: - | ||
|
@@ -144,6 +144,7 @@ private extension Example1TemplateContent { | |
let package: Package | ||
let localization: ProcessedLocalizedConfiguration | ||
let configuration: PaywallData.Configuration | ||
let headerImageURL: URL | ||
} | ||
|
||
enum Error: Swift.Error { | ||
|
@@ -173,9 +174,15 @@ private extension Example1TemplateContent { | |
} | ||
|
||
@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) | ||
extension Example1TemplateContent.Error { | ||
extension Example1TemplateContent.Error: CustomNSError { | ||
|
||
var errorUserInfo: [String: Any] { | ||
return [ | ||
NSLocalizedDescriptionKey: self.description | ||
] | ||
} | ||
|
||
var description: String { | ||
private var description: String { | ||
switch self { | ||
case .noPackages: | ||
return "Attempted to display paywall with no packages." | ||
|
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 is fine for now, but I think sooner or later we'll have to move into a system that pre-downloads and caches the image. Paywalls need to have the smallest possible wait time to load
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.
Yup 👍🏻