Skip to content
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

[Customer Center] Create CustomerCenterView #3919

Merged
merged 62 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
3018b86
Create CustomerCenterView
vegaro May 28, 2024
35f53cb
lint autofix
vegaro May 28, 2024
2b1d692
linting cleanup
vegaro May 28, 2024
7dd25b4
update email
vegaro May 29, 2024
15e8152
fix compilation
vegaro May 29, 2024
b062981
Update RevenueCatUI/CustomerCenter/Data/CustomerCenterData.swift
vegaro May 29, 2024
6c7aa97
rename areSubscriptionsFromApple
vegaro May 29, 2024
68f0e4c
made error private
vegaro May 29, 2024
81fe662
made state Published
vegaro May 29, 2024
7e711f1
created CustomerCenterError and feedback on ManageSubscriptionsViewModel
vegaro May 29, 2024
b254d0b
remove init from CustomerCenterView
vegaro May 29, 2024
327bbb7
use task instead of onAppear
vegaro May 29, 2024
750ce43
Apply suggestions from code review
vegaro May 29, 2024
04697cc
inits cleanup
vegaro May 29, 2024
f32c104
move handleAction to viewModel
vegaro May 30, 2024
2eec857
more user-friendly name
vegaro Jun 4, 2024
b9e93e2
use morphology
vegaro Jun 4, 2024
7993d68
actually remove it's not needed
vegaro Jun 4, 2024
6c7feea
better wording
vegaro Jun 4, 2024
01d9322
create HelpPathDetail
vegaro Jun 4, 2024
ef48028
fix some data types
vegaro Jun 4, 2024
145113c
add docs
vegaro Jun 4, 2024
717f4fd
mode is an enum
vegaro Jun 5, 2024
c17abbe
cleanup and availability
vegaro Jun 6, 2024
3e7653d
add headers and renames
vegaro Jun 6, 2024
5612874
remove promotionals
vegaro Jun 6, 2024
1b00a49
improve loadHasSubscriptions logic
vegaro Jun 6, 2024
33274d1
cleanup SubscriptionInformation
vegaro Jun 6, 2024
60824c3
fix linter
vegaro Jun 6, 2024
cb65380
add return
vegaro Jun 6, 2024
a1461bf
fix availability
vegaro Jun 6, 2024
b7587cd
fix body public
vegaro Jun 6, 2024
4cc7c44
fix colors
vegaro Jun 7, 2024
71469be
clean up test config data
vegaro Jun 7, 2024
f1a99d5
publish on main thread
vegaro Jun 7, 2024
90e8eda
remove button from view
vegaro Jun 7, 2024
c911bc5
fix date
vegaro Jun 7, 2024
f429f6d
remove contact support buttons
vegaro Jun 7, 2024
e285d64
remove unnecessary do catch
vegaro Jun 7, 2024
1749c10
cleanup
vegaro Jun 10, 2024
0fe1f0a
fix visionOS
vegaro Jun 10, 2024
fbfd704
make viewmodels MainActor
vegaro Jun 11, 2024
b07f8cf
CustomerCenterViewModelTests
vegaro Jun 11, 2024
49704e7
Make Viewmodel testable
vegaro Jun 12, 2024
6c25f9e
lint fix
vegaro Jun 12, 2024
958b65d
check for availability
vegaro Jun 12, 2024
f75cfc0
fix macos
vegaro Jun 12, 2024
b1c0ac7
refundRequestStatusMessage
vegaro Jun 12, 2024
9851c1f
CustomerCenterConfigTestData debug only
vegaro Jun 12, 2024
72154ce
extracts strings
vegaro Jun 12, 2024
9752176
add lines
vegaro Jun 13, 2024
5115202
more strings
vegaro Jun 13, 2024
1098378
remove LocalizedString
vegaro Jun 13, 2024
b5955d6
@jamesrb1 suggestions
vegaro Jun 26, 2024
0bf3757
lint
vegaro Jun 26, 2024
98bb9f2
&& (swift(>=5.9) && !os(visionOS))
vegaro Jun 26, 2024
c07bb1d
fix linter
vegaro Jun 26, 2024
7955877
fix macros
vegaro Jun 26, 2024
21b52f6
fix tests
vegaro Jun 26, 2024
6a6b003
fix tests
vegaro Jun 26, 2024
239f10a
change localization of date
vegaro Jun 26, 2024
8f21718
fix pod lib lint
vegaro Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@
3543914226F911F300E669DF /* MockSK1Product.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DDF41E524F6F5DC005BC22D /* MockSK1Product.swift */; };
3543914426F911F300E669DF /* MockCustomerInfoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 351B514826D44A2F00BD2BD7 /* MockCustomerInfoManager.swift */; };
3543914526F926D900E669DF /* SKProductSubscriptionDurationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DDF41E924F6F844005BC22D /* SKProductSubscriptionDurationExtensions.swift */; };
3544DA6D2C2C848E00704E9D /* CustomerCenterViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3544DA692C2C848E00704E9D /* CustomerCenterViewModelTests.swift */; };
3544DA6F2C2C848E00704E9D /* ManageSubscriptionsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3544DA6A2C2C848E00704E9D /* ManageSubscriptionsViewModelTests.swift */; };
354895D4267AE4B4001DC5B1 /* AttributionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 354895D3267AE4B4001DC5B1 /* AttributionKey.swift */; };
354895D6267BEDE3001DC5B1 /* ReservedSubscriberAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 354895D5267BEDE3001DC5B1 /* ReservedSubscriberAttributes.swift */; };
35549323269E298B005F9AE9 /* OfferingsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35549322269E298B005F9AE9 /* OfferingsFactory.swift */; };
Expand Down Expand Up @@ -1117,6 +1119,8 @@
352B7D7827BD919B002A47DD /* DangerousSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DangerousSettings.swift; sourceTree = "<group>"; };
3530C18822653E8F00D6DF52 /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; };
35316DA82BD14BFD00E4A970 /* MockDiagnosticsSynchronizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDiagnosticsSynchronizer.swift; sourceTree = "<group>"; };
3544DA692C2C848E00704E9D /* CustomerCenterViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomerCenterViewModelTests.swift; sourceTree = "<group>"; };
3544DA6A2C2C848E00704E9D /* ManageSubscriptionsViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManageSubscriptionsViewModelTests.swift; sourceTree = "<group>"; };
354895D3267AE4B4001DC5B1 /* AttributionKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributionKey.swift; sourceTree = "<group>"; };
354895D5267BEDE3001DC5B1 /* ReservedSubscriberAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservedSubscriberAttributes.swift; sourceTree = "<group>"; };
35549322269E298B005F9AE9 /* OfferingsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfferingsFactory.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2525,6 +2529,15 @@
path = Purchasing;
sourceTree = "<group>";
};
3544DA6B2C2C848E00704E9D /* CustomerCenter */ = {
isa = PBXGroup;
children = (
3544DA692C2C848E00704E9D /* CustomerCenterViewModelTests.swift */,
3544DA6A2C2C848E00704E9D /* ManageSubscriptionsViewModelTests.swift */,
);
path = CustomerCenter;
sourceTree = "<group>";
};
354895D0267AE32D001DC5B1 /* SubscriberAttributes */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -3415,6 +3428,7 @@
887A62242C1D168B00E1A461 /* RevenueCatUITests */ = {
isa = PBXGroup;
children = (
3544DA6B2C2C848E00704E9D /* CustomerCenter */,
887A612D2C1D168B00E1A461 /* Data */,
887A61362C1D168B00E1A461 /* Helpers */,
887A61382C1D168B00E1A461 /* Purchasing */,
Expand Down Expand Up @@ -5028,8 +5042,10 @@
887A63352C1D177800E1A461 /* OSVersionEquivalent.swift in Sources */,
887A63362C1D177800E1A461 /* SnapshotTesting+Extensions.swift in Sources */,
887A63372C1D177800E1A461 /* TestCase.swift in Sources */,
3544DA6F2C2C848E00704E9D /* ManageSubscriptionsViewModelTests.swift in Sources */,
887A63382C1D177800E1A461 /* PurchaseHandlerTests.swift in Sources */,
887A63392C1D177800E1A461 /* OtherPaywallViewTests.swift in Sources */,
3544DA6D2C2C848E00704E9D /* CustomerCenterViewModelTests.swift in Sources */,
887A633A2C1D177800E1A461 /* PaywallViewDynamicTypeTests.swift in Sources */,
887A633B2C1D177800E1A461 /* PaywallViewLocalizationTests.swift in Sources */,
887A633C2C1D177800E1A461 /* Template1ViewTests.swift in Sources */,
Expand Down
59 changes: 57 additions & 2 deletions RevenueCatUI/CustomerCenter/Data/CustomerCenterConfigData.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,63 @@
//
// File.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
//
// CustomerCenterConfigData.swift
//
//
// Created by Cesar de la Vega on 17/6/24.
// Created by Cesar de la Vega on 28/5/24.
//

import Foundation
import RevenueCat

struct CustomerCenterConfigData {

let id: String
let paths: [HelpPath]
let title: String

enum HelpPathType: String {
case missingPurchase = "MISSING_PURCHASE"
case refundRequest = "REFUND_REQUEST"
case changePlans = "CHANGE_PLANS"
case cancel = "CANCEL"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I was using lowercase for these in the backend... I'm ok changing them to uppercase though 👍

case unknown
}

enum HelpPathDetail {

case promotionalOffer(PromotionalOffer)
case feedbackSurvey(FeedbackSurvey)

}

struct HelpPath {

let id: String
let title: String
let type: HelpPathType
let detail: HelpPathDetail?

}

struct FeedbackSurvey {

let title: String
let options: [FeedbackSurveyOption]

}

struct FeedbackSurveyOption {

let id: String
let title: String

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// 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
//
// CustomerCenterConfigTestData.swift
//
//
// Created by Cesar de la Vega on 28/5/24.
//

import Foundation
import RevenueCat

enum CustomerCenterConfigTestData {
vegaro marked this conversation as resolved.
Show resolved Hide resolved

@available(iOS 14.0, *)
static let customerCenterData = CustomerCenterConfigData(
id: "customer_center_id",
paths: [
.init(
id: "1",
title: "Didn't receive purchase",
type: .missingPurchase,
detail: nil
),
.init(
id: "2",
title: "Request a refund",
type: .refundRequest,
detail: nil
),
.init(
id: "3",
title: "Change plans",
type: .changePlans,
detail: nil
),
.init(
id: "4",
title: "Cancel subscription",
type: .cancel,
detail: .feedbackSurvey(.init(
title: "Why are you cancelling?",
options: [
.init(
id: "1",
title: "Too expensive"
),
.init(
id: "2",
title: "Don't use the app"
),
.init(
id: "3",
title: "Bought by mistake"
)
]
))
)
],
title: "How can we help?"
)

static let subscriptionInformation: SubscriptionInformation = .init(
title: "Basic",
durationTitle: "Monthly",
price: "$4.99 / month",
nextRenewalString: "June 1st, 2024",
willRenew: true,
productIdentifier: "product_id",
active: true
)

}
41 changes: 41 additions & 0 deletions RevenueCatUI/CustomerCenter/Data/CustomerCenterError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// 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
//
// CustomerCenterError.swift
//
//
// Created by Cesar de la Vega on 29/5/24.
//

import Foundation

/// Error produced when displaying the customer center.
enum CustomerCenterError: Error {

/// Could not find information for an active subscription.
case couldNotFindSubscriptionInformation

}

extension CustomerCenterError: CustomNSError {

var errorUserInfo: [String: Any] {
return [
NSLocalizedDescriptionKey: self.description
]
}

private var description: String {
switch self {
case .couldNotFindSubscriptionInformation:
return "Could not find information for an active subscription."
}
}

}
50 changes: 50 additions & 0 deletions RevenueCatUI/CustomerCenter/Data/SubscriptionInformation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// 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
//
// SubscriptionInformation.swift
//
//
// Created by Cesar de la Vega on 28/5/24.
//

import Foundation

struct SubscriptionInformation {

let title: String
let durationTitle: String
let price: String
let nextRenewalString: String?
let productIdentifier: String

var renewalString: String {
return active ? (willRenew ? "Renews" : "Expires") : "Expired"
}

private let willRenew: Bool
private let active: Bool

init(title: String,
durationTitle: String,
price: String,
nextRenewalString: String?,
willRenew: Bool,
productIdentifier: String,
active: Bool
) {
self.title = title
self.durationTitle = durationTitle
self.price = price
self.nextRenewalString = nextRenewalString
self.productIdentifier = productIdentifier
self.willRenew = willRenew
self.active = active
}

}
50 changes: 50 additions & 0 deletions RevenueCatUI/CustomerCenter/ManageSubscriptionsButtonStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// 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
//
// CustomButtonStyle.swift
//
//
// Created by Cesar de la Vega on 28/5/24.
//

import Foundation
import SwiftUI

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
struct ManageSubscriptionsButtonStyle: ButtonStyle {

func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding()
.frame(width: 300)
.background(Color.accentColor)
.foregroundColor(.white)
.cornerRadius(10)
.scaleEffect(configuration.isPressed ? 0.95 : 1.0)
.opacity(configuration.isPressed ? 0.8 : 1.0)
.animation(.easeInOut(duration: 0.2), value: configuration.isPressed)
}

}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
struct CustomButtonStylePreview_Previews: PreviewProvider {

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

}
37 changes: 37 additions & 0 deletions RevenueCatUI/CustomerCenter/ManageSubscriptionsPurchaseType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// 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
//
// ManageSubscriptionsPurchaseType.swift
//
//
// Created by Cesar de la Vega on 12/6/24.
//

import Foundation
import RevenueCat

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
protocol ManageSubscriptionsPurchaseType: Sendable {

@Sendable
func customerInfo() async throws -> CustomerInfo

@Sendable
func products(_ productIdentifiers: [String]) async -> [StoreProduct]

@Sendable
func showManageSubscriptions() async throws

@Sendable
func beginRefundRequest(forProduct productID: String) async throws -> RefundRequestStatus

}
Loading