Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add colors support
Browse files Browse the repository at this point in the history
vegaro committed Jul 8, 2024
1 parent 95e3e4d commit 3fd6220
Showing 11 changed files with 138 additions and 104 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
Original file line number Diff line number Diff line change
@@ -86,17 +86,12 @@ enum CustomerCenterConfigTestData {
)
],
appearance: .init(
mode: .custom,
light: .init(
accentColor: "#ffffff",
backgroundColor: "#000000",
textColor: "#000000"
),
dark: .init(
accentColor: "#000000",
backgroundColor: "#ffffff",
textColor: "#ffffff"
)
mode: .custom(accentColor: try! RCColor(light: RCColor(stringRepresentation: "#ffffff"),
dark: try! RCColor(stringRepresentation: "#000000")),
backgroundColor: try! RCColor(light: RCColor(stringRepresentation: "#000000"),
dark: try! RCColor(stringRepresentation: "#ffffff")),
textColor: try! RCColor(light: RCColor(stringRepresentation: "#000000"),
dark: try! RCColor(stringRepresentation: "#ffffff")))
),
localization: .init(
locale: "en_US",
31 changes: 27 additions & 4 deletions RevenueCatUI/CustomerCenter/ManageSubscriptionsButtonStyle.swift
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@
//

import Foundation
import RevenueCat
import SwiftUI

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@@ -22,12 +23,17 @@ import SwiftUI
@available(watchOS, unavailable)
struct ManageSubscriptionsButtonStyle: ButtonStyle {

func makeBody(configuration: Configuration) -> some View {
var appearance: CustomerCenterConfigData.Appearance

@Environment(\.colorScheme)
private var colorScheme

func makeBody(configuration: ButtonStyleConfiguration) -> some View {
configuration.label
.padding()
.frame(width: 300)
.background(Color.accentColor)
.foregroundColor(.white)
.background(color(from: appearance))
.foregroundColor(colorScheme == .dark ? Color.black : Color.white)
.cornerRadius(10)
.scaleEffect(configuration.isPressed ? 0.95 : 1.0)
.opacity(configuration.isPressed ? 0.8 : 1.0)
@@ -36,6 +42,23 @@ struct ManageSubscriptionsButtonStyle: ButtonStyle {

}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
private extension ManageSubscriptionsButtonStyle {

func color(from appearance: CustomerCenterConfigData.Appearance) -> Color {
switch appearance.mode {
case .system:
Color.accentColor
case .custom(accentColor: let accentColor, backgroundColor: let backgroundColor, textColor: let textColor):
accentColor.underlyingColor
}
}

}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@@ -44,7 +67,7 @@ struct CustomButtonStylePreview_Previews: PreviewProvider {

static var previews: some View {
Button("Didn't receive purchase") {}
.buttonStyle(ManageSubscriptionsButtonStyle())
.buttonStyle(ManageSubscriptionsButtonStyle(appearance: CustomerCenterConfigData.Appearance(mode: .system)))
}

}
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@ import RevenueCat
class ManageSubscriptionsViewModel: ObservableObject {

let screen: CustomerCenterConfigData.Screen
let appearance: CustomerCenterConfigData.Appearance

@Published
var showRestoreAlert: Bool = false
@@ -66,25 +67,31 @@ class ManageSubscriptionsViewModel: ObservableObject {

private var error: Error?

convenience init(screen: CustomerCenterConfigData.Screen) {
convenience init(screen: CustomerCenterConfigData.Screen,
appearance: CustomerCenterConfigData.Appearance) {
self.init(screen: screen,
appearance: appearance,
purchasesProvider: ManageSubscriptionPurchases(),
promotionalOfferViewModel: PromotionalOfferViewModel())
}

// @PublicForExternalTesting
init(screen: CustomerCenterConfigData.Screen,
appearance: CustomerCenterConfigData.Appearance,
purchasesProvider: ManageSubscriptionsPurchaseType,
promotionalOfferViewModel: PromotionalOfferViewModel) {
self.state = .notLoaded
self.screen = screen
self.appearance = appearance
self.purchasesProvider = purchasesProvider
self.promotionalOfferViewModel = promotionalOfferViewModel
}

init(screen: CustomerCenterConfigData.Screen,
appearance: CustomerCenterConfigData.Appearance,
subscriptionInformation: SubscriptionInformation) {
self.screen = screen
self.appearance = appearance
self.subscriptionInformation = subscriptionInformation
self.purchasesProvider = ManageSubscriptionPurchases()
self.promotionalOfferViewModel = PromotionalOfferViewModel()
@@ -125,22 +132,9 @@ class ManageSubscriptionsViewModel: ObservableObject {
)
}

#if os(iOS) || targetEnvironment(macCatalyst)
func determineFlow(for path: CustomerCenterConfigData.HelpPath) async {
if case let .feedbackSurvey(feedbackSurvey) = path.detail {
self.feedbackSurveyData = FeedbackSurveyData(configuration: feedbackSurvey) { [weak self] in
Task {
await self?.performAction(for: path)
}
}
} else {
await self.performAction(for: path)
}
}

func handleSheetDismiss() {
func handleSheetDismiss() async {
if let loadingPath = loadingPath {
performAction(for: loadingPath)
await self.performAction(for: loadingPath)
self.loadingPath = nil
}
}
@@ -150,14 +144,16 @@ class ManageSubscriptionsViewModel: ObservableObject {
switch path.detail {
case let .feedbackSurvey(feedbackSurvey):
self.feedbackSurveyData = FeedbackSurveyData(configuration: feedbackSurvey) { [weak self] in
await self?.performAction(for: path)
Task {
await self?.performAction(for: path)
}
}
case let .promotionalOffer(promotionalOffer):
self.loadingPath = path
await promotionalOfferViewModel.loadPromo(promotionalOfferId: promotionalOffer.iosOfferId)
self.isShowingPromotionalOffer = true
default:
performAction(for: path)
await self.performAction(for: path)
}
}
#endif
2 changes: 1 addition & 1 deletion RevenueCatUI/CustomerCenter/Views/CustomerCenterView.swift
Original file line number Diff line number Diff line change
@@ -72,7 +72,7 @@ private extension CustomerCenterView {
if viewModel.hasSubscriptions {
if viewModel.subscriptionsAreFromApple,
let screen = configuration[.management] {
ManageSubscriptionsView(screen: screen)
ManageSubscriptionsView(screen: screen, appearance: configuration.appearance)
} else {
WrongPlatformView()
}
14 changes: 10 additions & 4 deletions RevenueCatUI/CustomerCenter/Views/FeedbackSurveyView.swift
Original file line number Diff line number Diff line change
@@ -27,9 +27,12 @@ struct FeedbackSurveyView: View {

@StateObject
private var viewModel: FeedbackSurveyViewModel
private let appearance: CustomerCenterConfigData.Appearance

init(feedbackSurveyData: FeedbackSurveyData) {
init(feedbackSurveyData: FeedbackSurveyData,
appearance: CustomerCenterConfigData.Appearance) {
self._viewModel = StateObject(wrappedValue: FeedbackSurveyViewModel(feedbackSurveyData: feedbackSurveyData))
self.appearance = appearance
}

var body: some View {
@@ -42,6 +45,7 @@ struct FeedbackSurveyView: View {

FeedbackSurveyButtonsView(options: self.viewModel.feedbackSurveyData.configuration.options,
action: self.viewModel.handleAction(for:),
appearance: self.appearance,
loadingStates: self.$viewModel.loadingStates)
}
.sheet(
@@ -51,8 +55,8 @@ struct FeedbackSurveyView: View {
if let promotionalOffer = self.viewModel.promotionalOffer,
let product = self.viewModel.product {
PromotionalOfferView(promotionalOffer: promotionalOffer,
product: product
)
product: product,
appearance: self.appearance)
}
})
}
@@ -68,6 +72,8 @@ struct FeedbackSurveyButtonsView: View {

let options: [CustomerCenterConfigData.HelpPath.FeedbackSurvey.Option]
let action: (CustomerCenterConfigData.HelpPath.FeedbackSurvey.Option) async -> Void
let appearance: CustomerCenterConfigData.Appearance

@Binding
var loadingStates: [String: Bool]

@@ -85,7 +91,7 @@ struct FeedbackSurveyButtonsView: View {
Text(option.title)
}
}
.buttonStyle(ManageSubscriptionsButtonStyle())
.buttonStyle(ManageSubscriptionsButtonStyle(appearance: appearance))
.disabled(self.loadingStates[option.id] ?? false)
}
}
18 changes: 12 additions & 6 deletions RevenueCatUI/CustomerCenter/Views/ManageSubscriptionsView.swift
Original file line number Diff line number Diff line change
@@ -31,8 +31,9 @@ struct ManageSubscriptionsView: View {
@StateObject
private var viewModel: ManageSubscriptionsViewModel

init(screen: CustomerCenterConfigData.Screen) {
let viewModel = ManageSubscriptionsViewModel(screen: screen)
init(screen: CustomerCenterConfigData.Screen,
appearance: CustomerCenterConfigData.Appearance) {
let viewModel = ManageSubscriptionsViewModel(screen: screen, appearance: appearance)
self._viewModel = .init(wrappedValue: viewModel)
}

@@ -63,7 +64,8 @@ struct ManageSubscriptionsView: View {

if let feedbackSurveyData = self.viewModel.feedbackSurveyData {
NavigationLink(
destination: FeedbackSurveyView(feedbackSurveyData: feedbackSurveyData)
destination: FeedbackSurveyView(feedbackSurveyData: feedbackSurveyData,
appearance: self.viewModel.appearance)
.onDisappear {
self.viewModel.feedbackSurveyData = nil
},
@@ -204,15 +206,18 @@ struct ManageSubscriptionButton: View {
}
.restorePurchasesAlert(isPresented: self.$viewModel.showRestoreAlert)
.sheet(isPresented: self.$viewModel.isShowingPromotionalOffer, onDismiss: {
self.viewModel.handleSheetDismiss()
Task {
await self.viewModel.handleSheetDismiss()
}
}, content: {
if let promotionalOffer = self.viewModel.promotionalOffer,
let product = self.viewModel.product {
PromotionalOfferView(promotionalOffer: promotionalOffer,
product: product)
product: product,
appearance: self.viewModel.appearance)
}
})
.buttonStyle(ManageSubscriptionsButtonStyle())
.buttonStyle(ManageSubscriptionsButtonStyle(appearance: self.viewModel.appearance))
.disabled(self.viewModel.loadingPath?.id == path.id)
}
}
@@ -229,6 +234,7 @@ struct ManageSubscriptionsView_Previews: PreviewProvider {
static var previews: some View {
let viewModel = ManageSubscriptionsViewModel(
screen: CustomerCenterConfigTestData.customerCenterData[.management]!,
appearance: CustomerCenterConfigTestData.customerCenterData.appearance,
subscriptionInformation: CustomerCenterConfigTestData.subscriptionInformation)
ManageSubscriptionsView(viewModel: viewModel)
}
15 changes: 7 additions & 8 deletions RevenueCatUI/CustomerCenter/Views/NoSubscriptionsView.swift
Original file line number Diff line number Diff line change
@@ -24,21 +24,20 @@ import SwiftUI
@available(watchOS, unavailable)
@available(visionOS, unavailable)
struct NoSubscriptionsView: View {

init(configuration: CustomerCenterConfigData) {
self.configuration = configuration
}

// swiftlint:disable:next todo
// TODO: build screen using this configuration
let configuration: CustomerCenterConfigData
private let configuration: CustomerCenterConfigData

@Environment(\.dismiss)
var dismiss

private var dismiss
@State
private var showRestoreAlert: Bool = false

init(configuration: CustomerCenterConfigData) {
self.configuration = configuration
}

var body: some View {
VStack {
Text("No Subscriptions found")
@@ -54,7 +53,7 @@ struct NoSubscriptionsView: View {
showRestoreAlert = true
}
.restorePurchasesAlert(isPresented: $showRestoreAlert)
.buttonStyle(ManageSubscriptionsButtonStyle())
.buttonStyle(ManageSubscriptionsButtonStyle(appearance: self.configuration.appearance))

Button("Cancel") {
dismiss()
13 changes: 10 additions & 3 deletions RevenueCatUI/CustomerCenter/Views/PromotionalOfferView.swift
Original file line number Diff line number Diff line change
@@ -26,23 +26,30 @@ import SwiftUI
@available(visionOS, unavailable)
struct PromotionalOfferView: View {

private let appearance: CustomerCenterConfigData.Appearance

@StateObject
private var viewModel: PromotionalOfferViewModel
@Environment(\.dismiss)
private var dismiss
private var promotionalOfferId: String

init(promotionalOfferId: String) {
init(promotionalOfferId: String,
appearance: CustomerCenterConfigData.Appearance) {
let viewModel = PromotionalOfferViewModel()
self._viewModel = StateObject(wrappedValue: viewModel)
self.promotionalOfferId = promotionalOfferId
self.appearance = appearance
}

init(promotionalOffer: PromotionalOffer, product: StoreProduct) {
init(promotionalOffer: PromotionalOffer,
product: StoreProduct,
appearance: CustomerCenterConfigData.Appearance) {
let viewModel = PromotionalOfferViewModel(product: product, promotionalOffer: promotionalOffer)
self._viewModel = StateObject(wrappedValue: viewModel)
// force unwrap since it is only `nil` for SK1 products before iOS 12.2.
self.promotionalOfferId = promotionalOffer.discount.offerIdentifier!
self.appearance = appearance
}

var body: some View {
@@ -74,7 +81,7 @@ struct PromotionalOfferView: View {
.font(.subheadline)
}
})
.buttonStyle(ManageSubscriptionsButtonStyle())
.buttonStyle(ManageSubscriptionsButtonStyle(appearance: self.appearance))

Button("No thanks") {
dismiss()
81 changes: 35 additions & 46 deletions Sources/CustomerCenter/CustomerCenterConfigData.swift
Original file line number Diff line number Diff line change
@@ -15,6 +15,9 @@

import Foundation

// swiftlint:disable missing_docs
public typealias RCColor = PaywallColor

// swiftlint:disable missing_docs
// swiftlint:disable nesting
public struct CustomerCenterConfigData {
@@ -130,45 +133,16 @@ public struct CustomerCenterConfigData {

public struct Appearance {

let mode: AppearanceMode
let light: AppearanceCustomColors
let dark: AppearanceCustomColors

public init(mode: AppearanceMode, light: AppearanceCustomColors, dark: AppearanceCustomColors) {
public let mode: AppearanceMode

public init(mode: AppearanceMode) {
self.mode = mode
self.light = light
self.dark = dark
}

public struct AppearanceCustomColors {

let accentColor: String
let backgroundColor: String
let textColor: String

public init(accentColor: String, backgroundColor: String, textColor: String) {
self.accentColor = accentColor
self.backgroundColor = backgroundColor
self.textColor = textColor
}

}

public enum AppearanceMode: String {

case custom = "CUSTOM"
case system = "SYSTEM"

init(from rawValue: String) {
switch rawValue {
case "CUSTOM":
self = .custom
case "SYSTEM":
self = .system
default:
self = .system
}
}
public enum AppearanceMode {

case system
case custom(accentColor: RCColor, backgroundColor: RCColor, textColor: RCColor)

}

@@ -217,6 +191,7 @@ public struct CustomerCenterConfigData {

}

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
extension CustomerCenterConfigData {

init(from response: CustomerCenterConfigResponse) {
@@ -243,24 +218,38 @@ extension CustomerCenterConfigData.Screen {

}

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
extension CustomerCenterConfigData.Appearance {

init(from response: CustomerCenterConfigResponse.Appearance) {
self.mode = CustomerCenterConfigData.Appearance.AppearanceMode(from: response.mode)
self.light = CustomerCenterConfigData.Appearance.AppearanceCustomColors(from: response.light)
self.dark = CustomerCenterConfigData.Appearance.AppearanceCustomColors(from: response.dark)
self.mode = CustomerCenterConfigData.Appearance.AppearanceMode(from: response)
}

}

extension CustomerCenterConfigData.Appearance.AppearanceCustomColors {
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
extension CustomerCenterConfigData.Appearance.AppearanceMode {

init(from response: CustomerCenterConfigResponse.Appearance.AppearanceCustomColors) {
// swiftlint:disable:next todo
// TODO: convert colors to PaywallColor (RCColor)
self.accentColor = response.accentColor
self.backgroundColor = response.backgroundColor
self.textColor = response.textColor
init(from response: CustomerCenterConfigResponse.Appearance) {
switch (response.mode) {
case .system:
self = .system
case .custom:
do {
let accent = RCColor(light: try RCColor(stringRepresentation: response.light.accentColor),
dark: try RCColor(stringRepresentation: response.dark.accentColor))
let background = RCColor(light: try RCColor(stringRepresentation: response.light.backgroundColor),
dark: try RCColor(stringRepresentation: response.dark.backgroundColor))
let text = RCColor(light: try RCColor(stringRepresentation: response.light.textColor),
dark: try RCColor(stringRepresentation: response.dark.textColor))
self = .custom(accentColor: accent,
backgroundColor: background,
textColor: text)
} catch {
Logger.error("Failed to parse appearance colors")
self = .system
}
}
}

}
14 changes: 11 additions & 3 deletions Sources/Networking/Responses/CustomerCenterConfigResponse.swift
Original file line number Diff line number Diff line change
@@ -80,16 +80,23 @@ struct CustomerCenterConfigResponse {

struct Appearance {

let mode: String
let mode: AppearanceMode
let light: AppearanceCustomColors
let dark: AppearanceCustomColors

struct AppearanceCustomColors {
enum AppearanceMode: String {

case system = "SYSTEM"
case custom = "CUSTOM"

}

struct AppearanceCustomColors {

let accentColor: String
let backgroundColor: String
let textColor: String

}

}
@@ -128,6 +135,7 @@ extension CustomerCenterConfigResponse.HelpPath.PromotionalOffer: Codable, Equat
extension CustomerCenterConfigResponse.HelpPath.FeedbackSurvey: Codable, Equatable {}
extension CustomerCenterConfigResponse.HelpPath.FeedbackSurvey.Option: Codable, Equatable {}
extension CustomerCenterConfigResponse.Appearance: Codable, Equatable {}
extension CustomerCenterConfigResponse.Appearance.AppearanceMode: Codable, Equatable {}
extension CustomerCenterConfigResponse.Appearance.AppearanceCustomColors: Codable, Equatable {}
extension CustomerCenterConfigResponse.Screen: Codable, Equatable {}
extension CustomerCenterConfigResponse.Screen.ScreenType: Codable, Equatable {}

0 comments on commit 3fd6220

Please sign in to comment.