mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
* Added root paywall background * Added some previews * Undo comment about fallback * Fix some lint * Fit lint... again * Added backgroud style to text and stack and fixed preview
163 lines
4.7 KiB
Swift
163 lines
4.7 KiB
Swift
//
|
|
// 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
|
|
//
|
|
// RemoteImage.swift
|
|
//
|
|
// Created by Nacho Soto on 7/19/23.
|
|
|
|
import SwiftUI
|
|
|
|
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
|
|
struct RemoteImage<Content: View>: View {
|
|
|
|
@Environment(\.colorScheme)
|
|
private var colorScheme
|
|
|
|
let url: URL
|
|
let lowResUrl: URL?
|
|
let darkUrl: URL?
|
|
let darkLowResUrl: URL?
|
|
let aspectRatio: CGFloat?
|
|
let maxWidth: CGFloat?
|
|
let content: (Image, CGSize) -> Content
|
|
|
|
@StateObject
|
|
private var highResLoader: ImageLoader = .init()
|
|
|
|
@StateObject
|
|
private var lowResLoader: ImageLoader = .init()
|
|
|
|
var fetchLowRes: Bool {
|
|
lowResUrl != nil
|
|
}
|
|
|
|
private let transition: AnyTransition = .opacity.animation(Constants.defaultAnimation)
|
|
|
|
init(
|
|
url: URL,
|
|
lowResUrl: URL? = nil,
|
|
darkUrl: URL? = nil,
|
|
darkLowResUrl: URL? = nil,
|
|
@ViewBuilder content: @escaping (Image, CGSize) -> Content
|
|
) {
|
|
self.url = url
|
|
self.lowResUrl = lowResUrl
|
|
self.darkUrl = darkUrl
|
|
self.darkLowResUrl = darkLowResUrl
|
|
self.content = content
|
|
self.aspectRatio = nil
|
|
self.maxWidth = nil
|
|
}
|
|
|
|
init(
|
|
url: URL,
|
|
lowResUrl: URL? = nil,
|
|
darkUrl: URL? = nil,
|
|
darkLowResUrl: URL? = nil,
|
|
aspectRatio: CGFloat? = nil,
|
|
maxWidth: CGFloat? = nil
|
|
) where Content == AnyView {
|
|
self.url = url
|
|
self.lowResUrl = lowResUrl
|
|
self.darkUrl = darkUrl
|
|
self.darkLowResUrl = darkLowResUrl
|
|
self.maxWidth = maxWidth
|
|
self.aspectRatio = aspectRatio
|
|
self.content = { (image, _) in
|
|
if let aspectRatio {
|
|
return AnyView(
|
|
image
|
|
.fitToAspect(aspectRatio, contentMode: .fill)
|
|
.frame(maxWidth: maxWidth)
|
|
.accessibilityHidden(true)
|
|
)
|
|
} else {
|
|
return AnyView(
|
|
image
|
|
.resizable()
|
|
.accessibilityHidden(true)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
Group {
|
|
if case let .success(result) = highResLoader.result {
|
|
content(result.image, result.size)
|
|
} else if case let .success(result) = lowResLoader.result {
|
|
content(result.image, result.size)
|
|
} else if case let .failure(highResError) = highResLoader.result {
|
|
if !fetchLowRes {
|
|
emptyView(error: highResError)
|
|
} else if case .failure = lowResLoader.result {
|
|
emptyView(error: highResError)
|
|
} else {
|
|
emptyView(error: nil)
|
|
}
|
|
} else {
|
|
emptyView(error: nil)
|
|
}
|
|
}
|
|
.transition(self.transition)
|
|
.task(id: self.url) { // This cancels the previous task when the URL changes.
|
|
switch self.colorScheme {
|
|
case .dark:
|
|
await loadImages(
|
|
url: self.darkUrl ?? self.url,
|
|
lowResUrl: self.darkLowResUrl ?? self.lowResUrl
|
|
)
|
|
case .light:
|
|
fallthrough
|
|
@unknown default:
|
|
await loadImages(
|
|
url: self.url,
|
|
lowResUrl: self.lowResUrl
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func loadImages(url: URL, lowResUrl: URL?) async {
|
|
if fetchLowRes, let lowResLoc = lowResUrl {
|
|
async let lowResLoad: Void = lowResLoader.load(url: lowResLoc)
|
|
async let highResLoad: Void = highResLoader.load(url: url)
|
|
_ = await (lowResLoad, highResLoad)
|
|
} else {
|
|
await highResLoader.load(url: url)
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func emptyView(error: Error?) -> some View {
|
|
let placeholderView = Rectangle()
|
|
.hidden()
|
|
|
|
Group {
|
|
if let aspectRatio {
|
|
placeholderView
|
|
.aspectRatio(aspectRatio, contentMode: .fit)
|
|
} else {
|
|
placeholderView
|
|
}
|
|
}
|
|
.frame(maxWidth: self.maxWidth)
|
|
.overlay {
|
|
Group {
|
|
if let error {
|
|
DebugErrorView("Error loading image from '\(self.url)': \(error)", releaseBehavior: .emptyView)
|
|
.font(.footnote)
|
|
.textCase(.none)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|