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

Create CustomerCenterView #3966

Closed
wants to merge 50 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
2eb5a25
Create CustomerCenterView
vegaro May 28, 2024
1e43ecc
lint autofix
vegaro May 28, 2024
ab9c5cf
linting cleanup
vegaro May 28, 2024
8b241b3
update email
vegaro May 29, 2024
66ff748
fix compilation
vegaro May 29, 2024
a9dd3a1
Update RevenueCatUI/CustomerCenter/Data/CustomerCenterData.swift
vegaro May 29, 2024
3526ba4
rename areSubscriptionsFromApple
vegaro May 29, 2024
8ce5ee9
made error private
vegaro May 29, 2024
248e9a1
made state Published
vegaro May 29, 2024
bf8b1cf
created CustomerCenterError and feedback on ManageSubscriptionsViewModel
vegaro May 29, 2024
aaa896b
remove init from CustomerCenterView
vegaro May 29, 2024
334cd5a
use task instead of onAppear
vegaro May 29, 2024
c3cea29
Apply suggestions from code review
vegaro May 29, 2024
ba9c5e2
inits cleanup
vegaro May 29, 2024
a08b427
move handleAction to viewModel
vegaro May 30, 2024
2f66496
more user-friendly name
vegaro Jun 4, 2024
6517627
use morphology
vegaro Jun 4, 2024
0986bcf
actually remove it's not needed
vegaro Jun 4, 2024
960ca81
better wording
vegaro Jun 4, 2024
553edba
create HelpPathDetail
vegaro Jun 4, 2024
7e75fcd
fix some data types
vegaro Jun 4, 2024
2d2834f
add docs
vegaro Jun 4, 2024
dd02073
mode is an enum
vegaro Jun 5, 2024
a1b470b
cleanup and availability
vegaro Jun 6, 2024
c350dd4
add headers and renames
vegaro Jun 6, 2024
fc6942a
remove promotionals
vegaro Jun 6, 2024
77be5bf
improve loadHasSubscriptions logic
vegaro Jun 6, 2024
bae682f
cleanup SubscriptionInformation
vegaro Jun 6, 2024
0af7fe2
fix linter
vegaro Jun 6, 2024
cf5386f
add return
vegaro Jun 6, 2024
e1466db
fix availability
vegaro Jun 6, 2024
86dd13d
fix body public
vegaro Jun 6, 2024
9bed3e4
fix colors
vegaro Jun 7, 2024
1fc6419
clean up test config data
vegaro Jun 7, 2024
db4a417
publish on main thread
vegaro Jun 7, 2024
2335cde
remove button from view
vegaro Jun 7, 2024
a2b1f59
fix date
vegaro Jun 7, 2024
ace6516
remove contact support buttons
vegaro Jun 7, 2024
1555bae
remove unnecessary do catch
vegaro Jun 7, 2024
0646d78
cleanup
vegaro Jun 10, 2024
33a9497
fix visionOS
vegaro Jun 10, 2024
973d7ec
make viewmodels MainActor
vegaro Jun 11, 2024
e616e44
CustomerCenterViewModelTests
vegaro Jun 11, 2024
dc8e855
Make Viewmodel testable
vegaro Jun 12, 2024
f507837
lint fix
vegaro Jun 12, 2024
509fe43
check for availability
vegaro Jun 12, 2024
01250f3
fix macos
vegaro Jun 12, 2024
11729d8
refundRequestStatusMessage
vegaro Jun 12, 2024
61bc934
CustomerCenterConfigTestData debug only
vegaro Jun 12, 2024
3f66cef
extracts strings
vegaro Jun 12, 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
70 changes: 70 additions & 0 deletions RevenueCatUI/CustomerCenter/Data/CustomerCenterConfigData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// 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 28/5/24.
//

import Foundation
import RevenueCat

struct CustomerCenterConfigData {

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

enum HelpPathType: String {
case missingPurchase = "MISSING_PURCHASE"
case refundRequest = "REFUND_REQUEST"
case changePlans = "CHANGE_PLANS"
case cancel = "CANCEL"
case unknown
}

enum HelpPathDetail {

case promotionalOffer(PromotionalOffer)
case feedbackSurvey(FeedbackSurvey)

}

struct HelpPath {

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

}

struct LocalizedString {

// swiftlint:disable:next identifier_name
let en_US: String

}

struct FeedbackSurvey {

let title: LocalizedString
let options: [FeedbackSurveyOption]

}

struct FeedbackSurveyOption {

let id: String
let title: LocalizedString

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// 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

#if DEBUG

enum CustomerCenterConfigTestData {

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

}

#endif
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

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

import Foundation

enum URLUtilities {

static func createMailURL() -> URL? {
let subject = "Support Request"
let body = "Please describe your issue or question."
let encodedSubject = subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
let encodedBody = body.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""

// swiftlint:disable:next todo
// TODO: make configurable
let urlString = "mailto:[email protected]?subject=\(encodedSubject)&body=\(encodedBody)"
return URL(string: urlString)
}

}
Loading