mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
189 lines
6.8 KiB
Swift
189 lines
6.8 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
|
|
//
|
|
// GradientView.swift
|
|
//
|
|
// Created by Mark Villacampa on 2024-11-24.
|
|
|
|
import SwiftUI
|
|
|
|
#if !os(macOS) && !os(tvOS) // For Paywalls V2
|
|
|
|
struct GradientView: View {
|
|
enum GradientStyle {
|
|
case linear(Int)
|
|
case radial
|
|
}
|
|
|
|
@Environment(\.colorScheme)
|
|
private var colorScheme
|
|
|
|
let lightGradient: Gradient
|
|
let darkGradient: Gradient?
|
|
let gradientStyle: GradientStyle
|
|
|
|
private var gradient: Gradient {
|
|
switch colorScheme {
|
|
case .light:
|
|
return lightGradient
|
|
case .dark:
|
|
return darkGradient ?? lightGradient
|
|
@unknown default:
|
|
return lightGradient
|
|
}
|
|
}
|
|
|
|
// Calculate the start and end points of the gradient following the CSS linear-gradient spec
|
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient
|
|
|
|
// Heavily inspired by this blog post by Mukhtar Bimurat
|
|
// https://link.medium.com/LczMO6j8YQb
|
|
private func calculatePoints(angle: Angle, rect: CGRect) -> (start: UnitPoint, end: UnitPoint) {
|
|
// Calculate the diagonal of the rectangle using the Pythagorean theorem
|
|
let diagonal = sqrt(pow(rect.width, 2) + pow(rect.height, 2))
|
|
// Calculates the angle between the rectangle's diagonal and its width
|
|
let angleBetweenDiagonalAndWidth = acos(rect.width / diagonal)
|
|
|
|
// Convert the angle to radians and negate to make clockwise
|
|
// Subtract 3π/2 (270 degrees) to follow CSS's angle convention.
|
|
let angleInRadians = -angle.radians - (3 * .pi / 2)
|
|
let normalizedAngle = angleInRadians.truncatingRemainder(dividingBy: .pi * 2)
|
|
|
|
// Calculate the angle between the diagonal and the gradient line
|
|
let angleBetweenDiagonalAndGradientLine: CGFloat
|
|
if (normalizedAngle > .pi / 2 && normalizedAngle < .pi) ||
|
|
(normalizedAngle > 3 * .pi / 2 && normalizedAngle < 2 * .pi) {
|
|
angleBetweenDiagonalAndGradientLine = CGFloat.pi - angleInRadians - angleBetweenDiagonalAndWidth
|
|
} else {
|
|
angleBetweenDiagonalAndGradientLine = angleInRadians - angleBetweenDiagonalAndWidth
|
|
}
|
|
|
|
// Get half the length of the gradient line, and calculate the vertical and horizontal offsets from the center
|
|
let halfGradientLine = abs(cos(angleBetweenDiagonalAndGradientLine) * diagonal) / 2
|
|
let horizontalOffset = halfGradientLine * cos(angleInRadians)
|
|
let verticalOffset = halfGradientLine * sin(angleInRadians)
|
|
|
|
// Convert the start and end points to UnitPoint coordinates (0-1 range)
|
|
let centerX = 0.5
|
|
let centerY = 0.5
|
|
let start = UnitPoint(x: centerX - horizontalOffset / rect.width, y: centerY + verticalOffset / rect.height)
|
|
let end = UnitPoint(x: centerX + horizontalOffset / rect.width, y: centerY - verticalOffset / rect.height)
|
|
|
|
return (start, end)
|
|
}
|
|
|
|
var body: some View {
|
|
switch gradientStyle {
|
|
case .linear(let degrees):
|
|
GeometryReader { geometry in
|
|
let points = calculatePoints(angle: .degrees(Double(degrees)), rect: geometry.frame(in: .local))
|
|
LinearGradient(
|
|
gradient: gradient,
|
|
startPoint: points.start,
|
|
endPoint: points.end
|
|
)
|
|
}
|
|
case .radial:
|
|
RadialGradient(
|
|
gradient: gradient,
|
|
center: .center,
|
|
startRadius: 0,
|
|
endRadius: 100
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
|
|
struct GradientView_Previews: PreviewProvider {
|
|
|
|
static private func gradientView(style: GradientView.GradientStyle) -> some View {
|
|
GradientView(
|
|
lightGradient: .init(stops: [
|
|
.init(color: .blue, location: 0),
|
|
.init(color: .red, location: 0.5),
|
|
.init(color: .black, location: 1)
|
|
]),
|
|
darkGradient: .init(stops: [
|
|
.init(color: .red, location: 0),
|
|
.init(color: .blue, location: 0.5),
|
|
.init(color: .black, location: 1)
|
|
]),
|
|
gradientStyle: style
|
|
)
|
|
}
|
|
|
|
static var previews: some View {
|
|
GradientView(
|
|
lightGradient: .init(colors: .init([.red, .white])),
|
|
darkGradient: .init(colors: .init([.blue, .white])),
|
|
gradientStyle: .radial
|
|
)
|
|
.preferredColorScheme(.dark)
|
|
.previewLayout(.sizeThatFits)
|
|
.previewDisplayName("Radial - Dark (should be blue)")
|
|
|
|
GradientView(
|
|
lightGradient: .init(colors: .init([.red, .white])),
|
|
darkGradient: .init(colors: .init([.blue, .white])),
|
|
gradientStyle: .radial
|
|
)
|
|
.previewLayout(.sizeThatFits)
|
|
.previewDisplayName("Radial - Light (should be red)")
|
|
|
|
GradientView(
|
|
lightGradient: .init(colors: .init([.red, .white])),
|
|
darkGradient: .init(colors: .init([.blue, .white])),
|
|
gradientStyle: .linear(45)
|
|
)
|
|
.preferredColorScheme(.dark)
|
|
.previewLayout(.sizeThatFits)
|
|
.previewDisplayName("Linear 45º - Dark (should be blue)")
|
|
|
|
GradientView(
|
|
lightGradient: .init(colors: .init([.red, .white])),
|
|
darkGradient: .init(colors: .init([.blue, .white])),
|
|
gradientStyle: .linear(90)
|
|
)
|
|
.previewLayout(.sizeThatFits)
|
|
.previewDisplayName("Linear 90º - Light (should be red)")
|
|
|
|
VStack {
|
|
LinearGradientPreview(label: "Linear 0º", degrees: 0)
|
|
LinearGradientPreview(label: "Linear 45º", degrees: 45)
|
|
LinearGradientPreview(label: "Linear 90º", degrees: 90)
|
|
LinearGradientPreview(label: "Linear 135º", degrees: 135)
|
|
LinearGradientPreview(label: "Linear 180º", degrees: 180)
|
|
LinearGradientPreview(label: "Linear 225º", degrees: 225)
|
|
LinearGradientPreview(label: "Linear 270º", degrees: 270)
|
|
}
|
|
.previewLayout(.sizeThatFits)
|
|
.previewDisplayName("Linear")
|
|
}
|
|
|
|
/// Helper view to preview linear gradients with different angles. This is useful so to keep down the
|
|
/// number of views in the container holding all of the previews to avoid compilation issues
|
|
/// with the preview.
|
|
private struct LinearGradientPreview: View {
|
|
|
|
let label: String
|
|
let degrees: Int
|
|
|
|
var body: some View {
|
|
VStack {
|
|
Text(label)
|
|
gradientView(style: .linear(degrees))
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|