Skip to content

Commit

Permalink
[Customer Center] Build feedback survey from JSON (#3959)
Browse files Browse the repository at this point in the history
Based off #3933 



https://github.com/RevenueCat/purchases-ios/assets/664544/11eae984-294a-4e14-8c40-7c2d50994c09

Can probably use some animations, but tuning that up will come up later

It will open a feedback survey for an option if there's one
  • Loading branch information
vegaro committed Jul 17, 2024
1 parent 9bc2c08 commit fec9486
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 5 deletions.
8 changes: 8 additions & 0 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@
35B745A82711001A00458D46 /* MockManageSubscriptionsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E840CD2710E2EB00899AE2 /* MockManageSubscriptionsHelper.swift */; };
35C05DC02BC84F5800109308 /* DiagnosticsSynchronizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C05DBF2BC84F5800109308 /* DiagnosticsSynchronizerTests.swift */; };
35C05DC82BC8510000109308 /* DiagnosticsTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C05DC72BC8510000109308 /* DiagnosticsTrackerTests.swift */; };
35C200AF2C39252D00B9778B /* FeedbackSurveyData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C200AE2C39252D00B9778B /* FeedbackSurveyData.swift */; };
35C200B12C39254100B9778B /* FeedbackSurveyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C200B02C39254100B9778B /* FeedbackSurveyView.swift */; };
35C272A12BC4084C005A0CE8 /* MockDiagnosticsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C272A02BC4084C005A0CE8 /* MockDiagnosticsTracker.swift */; };
35C272A22BC4084C005A0CE8 /* MockDiagnosticsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C272A02BC4084C005A0CE8 /* MockDiagnosticsTracker.swift */; };
35D0E5D026A5886C0099EAD8 /* ErrorUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35D0E5CF26A5886C0099EAD8 /* ErrorUtils.swift */; };
Expand Down Expand Up @@ -1194,6 +1196,8 @@
35AB6D392BBEE3150076B103 /* DiagnosticsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticsTracker.swift; sourceTree = "<group>"; };
35C05DBF2BC84F5800109308 /* DiagnosticsSynchronizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticsSynchronizerTests.swift; sourceTree = "<group>"; };
35C05DC72BC8510000109308 /* DiagnosticsTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticsTrackerTests.swift; sourceTree = "<group>"; };
35C200AE2C39252D00B9778B /* FeedbackSurveyData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackSurveyData.swift; sourceTree = "<group>"; };
35C200B02C39254100B9778B /* FeedbackSurveyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackSurveyView.swift; sourceTree = "<group>"; };
35C272A02BC4084C005A0CE8 /* MockDiagnosticsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDiagnosticsTracker.swift; sourceTree = "<group>"; };
35D0E5CF26A5886C0099EAD8 /* ErrorUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorUtils.swift; sourceTree = "<group>"; };
35D159CA2BC4396F004D8061 /* DiagnosticsPostOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticsPostOperation.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2549,6 +2553,7 @@
children = (
353756532C382C2800A1B8D6 /* CustomerCenterConfigTestData.swift */,
353756542C382C2800A1B8D6 /* CustomerCenterError.swift */,
35C200AE2C39252D00B9778B /* FeedbackSurveyData.swift */,
353756552C382C2800A1B8D6 /* SubscriptionInformation.swift */,
);
path = Data;
Expand All @@ -2568,6 +2573,7 @@
isa = PBXGroup;
children = (
3537565B2C382C2800A1B8D6 /* CustomerCenterView.swift */,
35C200B02C39254100B9778B /* FeedbackSurveyView.swift */,
3537565C2C382C2800A1B8D6 /* ManageSubscriptionsView.swift */,
3537565D2C382C2800A1B8D6 /* NoSubscriptionsView.swift */,
3537565E2C382C2800A1B8D6 /* RestorePurchasesAlert.swift */,
Expand Down Expand Up @@ -5209,6 +5215,7 @@
887A60C12C1D037000E1A461 /* DebugErrorView.swift in Sources */,
887A607C2C1D037000E1A461 /* ColorInformation+MultiScheme.swift in Sources */,
887A60782C1D037000E1A461 /* TestData.swift in Sources */,
35C200B12C39254100B9778B /* FeedbackSurveyView.swift in Sources */,
887A60672C1D037000E1A461 /* PaywallError.swift in Sources */,
88A543E52C37A4AF0039C6A5 /* ConsistentTierContentView.swift in Sources */,
887A606E2C1D037000E1A461 /* LocalizedAlertError.swift in Sources */,
Expand All @@ -5217,6 +5224,7 @@
887A60832C1D037000E1A461 /* VersionDetector.swift in Sources */,
887A60872C1D037000E1A461 /* ViewExtensions.swift in Sources */,
353756712C382C2800A1B8D6 /* ManageSubscriptionsPurchaseType.swift in Sources */,
35C200AF2C39252D00B9778B /* FeedbackSurveyData.swift in Sources */,
887A60BA2C1D037000E1A461 /* Template3View.swift in Sources */,
887A607D2C1D037000E1A461 /* ImageLoader.swift in Sources */,
887A60822C1D037000E1A461 /* PreviewHelpers.swift in Sources */,
Expand Down
37 changes: 37 additions & 0 deletions RevenueCatUI/CustomerCenter/Data/FeedbackSurveyData.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
//
// FeedbackSurveyData.swift
//
//
// Created by Cesar de la Vega on 14/6/24.
//

import Foundation
import RevenueCat

#if os(iOS)

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
class FeedbackSurveyData: ObservableObject {

var configuration: CustomerCenterConfigData.HelpPath.FeedbackSurvey
var action: (() -> Void)

init(configuration: CustomerCenterConfigData.HelpPath.FeedbackSurvey, action: @escaping (() -> Void)) {
self.configuration = configuration
self.action = action
}

}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//
// https://opensource.org/licenses/MIT
//
// CustomButtonStyle.swift
// ManageSubscriptionsButtonStyle.swift
//
//
// Created by Cesar de la Vega on 28/5/24.
Expand Down Expand Up @@ -40,7 +40,7 @@ struct ManageSubscriptionsButtonStyle: ButtonStyle {
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
struct CustomButtonStylePreview_Previews: PreviewProvider {
struct ManageSubscriptionsButtonStyle_Previews: PreviewProvider {

static var previews: some View {
Button("Didn't receive purchase") {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class ManageSubscriptionsViewModel: ObservableObject {

@Published
var showRestoreAlert: Bool = false
@Published
var feedbackSurveyData: FeedbackSurveyData?

@Published
var state: CustomerCenterViewState {
didSet {
Expand Down Expand Up @@ -105,7 +108,19 @@ class ManageSubscriptionsViewModel: ObservableObject {
}

#if os(iOS) || targetEnvironment(macCatalyst)
func handleAction(for path: CustomerCenterConfigData.HelpPath) async {
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 performAction(for path: CustomerCenterConfigData.HelpPath) async {
switch path.type {
case .missingPurchase:
self.showRestoreAlert = true
Expand Down
82 changes: 82 additions & 0 deletions RevenueCatUI/CustomerCenter/Views/FeedbackSurveyView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// 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
//
// FeedbackSurveyView.swift
//
//
// Created by Cesar de la Vega on 12/6/24.
//

import RevenueCat
import SwiftUI

#if os(iOS)

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
@available(visionOS, unavailable)
struct FeedbackSurveyView: View {

@ObservedObject
var feedbackSurveyData: FeedbackSurveyData

var body: some View {
VStack {
Text(feedbackSurveyData.configuration.title)
.font(.title)
.padding()

Spacer()

FeedbackSurveyButtonsView(options: feedbackSurveyData.configuration.options,
action: feedbackSurveyData.action)
}
}

}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
@available(visionOS, unavailable)
struct FeedbackSurveyButtonsView: View {

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

var body: some View {
VStack(spacing: Self.buttonSpacing) {
ForEach(options, id: \.id) { option in
AsyncButton(action: {
self.action()
}, label: {
Text(option.title)
})
.buttonStyle(ManageSubscriptionsButtonStyle())
}
}
}

}

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

private static let buttonSpacing: CGFloat = 16

}

#endif
37 changes: 35 additions & 2 deletions RevenueCatUI/CustomerCenter/Views/ManageSubscriptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,38 @@ struct ManageSubscriptionsView: View {
}

var body: some View {
if #available(iOS 16.0, *) {
NavigationStack {
content
.navigationDestination(isPresented: .constant(self.viewModel.feedbackSurveyData != nil)) {
if let feedbackSurveyData = self.viewModel.feedbackSurveyData {
FeedbackSurveyView(feedbackSurveyData: feedbackSurveyData)
.onDisappear {
self.viewModel.feedbackSurveyData = nil
}
}
}
}
} else {
NavigationView {
content
.background(NavigationLink(
destination: self.viewModel.feedbackSurveyData.map { data in
FeedbackSurveyView(feedbackSurveyData: data)
.onDisappear {
self.viewModel.feedbackSurveyData = nil
}
},
isActive: .constant(self.viewModel.feedbackSurveyData != nil)
) {
EmptyView()
})
}
}
}

@ViewBuilder
var content: some View {
VStack {
if self.viewModel.isLoaded {
HeaderView(viewModel: self.viewModel)
Expand All @@ -53,6 +85,7 @@ struct ManageSubscriptionsView: View {
Spacer()

ManageSubscriptionsButtonsView(viewModel: self.viewModel)

} else {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
Expand All @@ -61,8 +94,8 @@ struct ManageSubscriptionsView: View {
.task {
await loadInformationIfNeeded()
}
.navigationBarTitleDisplayMode(.inline)
}

}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
Expand Down Expand Up @@ -158,7 +191,7 @@ struct ManageSubscriptionsButtonsView: View {
}
ForEach(filteredPaths, id: \.id) { path in
AsyncButton(action: {
await self.viewModel.handleAction(for: path)
await self.viewModel.determineFlow(for: path)
}, label: {
Text(path.title)
})
Expand Down

0 comments on commit fec9486

Please sign in to comment.