-
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] Add purchase button activity indicator #4787
Changes from all commits
ee1ba97
b5ac347
fa40042
957fa9c
476ccc0
a753498
7770f2c
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,100 @@ | ||
// | ||
// 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 | ||
// | ||
// ProgressViewModifier.swift | ||
// | ||
// Created by Josh Holtz on 2/13/25. | ||
|
||
#if !os(macOS) && !os(tvOS) // For Paywalls V2 | ||
|
||
import SwiftUI | ||
|
||
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) | ||
struct ProgressViewModifier: ViewModifier { | ||
|
||
@Environment(\.colorScheme) | ||
private var colorScheme | ||
|
||
var backgroundStyle: BackgroundStyle? | ||
|
||
func body(content: Content) -> some View { | ||
content | ||
#if !os(watchOS) | ||
.background(.ultraThinMaterial) | ||
#endif | ||
.overlay(progressView) | ||
} | ||
|
||
@ViewBuilder | ||
private var progressView: some View { | ||
switch backgroundStyle { | ||
case .color(let displayableColorScheme): | ||
let colorInfo = displayableColorScheme.effectiveColor(for: colorScheme) | ||
ProgressView() | ||
.progressViewStyle(CircularProgressViewStyle(tint: bestTint(for: colorInfo))) | ||
case .image, .none: | ||
ProgressView() | ||
.progressViewStyle(CircularProgressViewStyle(tint: .white)) | ||
} | ||
} | ||
|
||
private func bestTint(for colorInfo: DisplayableColorInfo) -> Color { | ||
switch colorInfo { | ||
case .hex: | ||
return colorInfo.toColor(fallback: .black).brightness() > 0.6 ? .black : .white | ||
case .linear, .radial: | ||
let gradient = colorInfo.toGradient() | ||
let averageBrightness = gradient.stops | ||
.compactMap { $0.color.brightness() } | ||
.reduce(0, +) / CGFloat(gradient.stops.count) | ||
return averageBrightness > 0.6 ? .black : .white | ||
Comment on lines
+52
to
+56
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. I think this is ok as a first version and will work 99%, but perhaps we could try and be smarter about this, in the sense of giving the colors in the center a higher weight when computing the 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. I would love to be smarter about this in the future 😅 It's not perfect but its something 🤷♂️ 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. Totally! |
||
} | ||
} | ||
|
||
} | ||
|
||
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) | ||
private extension Color { | ||
|
||
/// Calculates the perceived brightness of the color. | ||
/// Uses the standard luminance formula for relative brightness perception. | ||
func brightness() -> CGFloat { | ||
#if os(macOS) | ||
guard let nsColor = NSColor(self).usingColorSpace(.deviceRGB) else { return 1.0 } | ||
let red = nsColor.redComponent | ||
let green = nsColor.greenComponent | ||
let blue = nsColor.blueComponent | ||
#else | ||
guard let uiColor = UIColor(self).cgColor.components, uiColor.count >= 3 else { return 1.0 } | ||
let red = uiColor[0] | ||
let green = uiColor[1] | ||
let blue = uiColor[2] | ||
#endif | ||
|
||
// Standard luminance coefficients for sRGB (per ITU-R BT.709) | ||
let redCoefficient: CGFloat = 0.299 | ||
let greenCoefficient: CGFloat = 0.587 | ||
let blueCoefficient: CGFloat = 0.114 | ||
|
||
// Compute brightness using the weighted sum of RGB components | ||
return (red * redCoefficient) + (green * greenCoefficient) + (blue * blueCoefficient) | ||
} | ||
|
||
} | ||
|
||
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) | ||
extension View { | ||
|
||
func progressOverlay(for backgroundStyle: BackgroundStyle?) -> some View { | ||
self.modifier(ProgressViewModifier(backgroundStyle: backgroundStyle)) | ||
} | ||
|
||
} | ||
|
||
#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.
Not sure if the
tint
here should depend on thecolorScheme
and/or on something else. You know, to prevent showing the while progress view on a white background. But then, I don't even know if this case is even possible (looking at the Dashboard, it seems like Buttons only support color-based backgrounds)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.
Yea I think the best version would be to base the tint on the image pixels underneath the circular progress view. But that can also come in a next iteration imo.
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.
Or actually, @joshdholtz are there any plans on making the loading indicator have a configurable color on the dashboard? That would avoid the need for us to be smart about it.
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.
If not, another way of trying to be smart about it could be to try and use the same color as the (first?) text component in the button. But I'm sure there are edge cases for this (e.g. no text or multiple text components). Just putting ideas out there
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.
@JayShortway I didn't want to because its not something our users should have to think about but I'm not opposed if we make it an optional style at some point
@ajpallares The calculated tint does take the color scheme into effect so we should be good!
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.
Also, I added a blur to the background it so it should make it even easier for either tint color to be visible 🤞