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

Support custom URL paths in ManageSubscriptionsView #4382

Merged
merged 9 commits into from
Oct 21, 2024

Conversation

tonidero
Copy link
Contributor

@tonidero tonidero commented Oct 18, 2024

Description

This allows to specify paths with custom URLs in the manage subscriptions view which can be opened either externally or in_app as a bottom sheet.

IN_APP:

Simulator.Screen.Recording.-.iPhone.16.-.2024-10-18.at.12.51.42.mp4

EXTERNAL:

Simulator.Screen.Recording.-.iPhone.16.-.2024-10-18.at.12.52.36.mp4

@@ -186,6 +200,18 @@ private extension ManageSubscriptionsViewModel {
} catch {
self.state = .error(error)
}
case .customUrl:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note this is only currently available in the ManageSubscriptionView. It won't be available in the NoSubscriptionsView or WrongPlatformView since we're not using the paths for those views.

Copy link
Member

Choose a reason for hiding this comment

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

interesting... It might be worth figuring out a way for those to work eventually since for those it might make even more sense - you can direct users to where you'd actually want them to manage

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I think we need to move some of the logic to a shared view for all 3 of them and handle paths in all 3. Right now the buttons are mostly hardcoded in those 2 other views. Probably not something for this PR though.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree we need to do that refactor yes

#endif

}

struct IdentifiableURL: Identifiable {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if this is the best way but I couldn't make URL Identifiable directly without getting a warning.

Copy link
Member

Choose a reason for hiding this comment

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

Are you getting the warning that it will not behave correctly if URL ever becomes Identifiable in the future? If so, yea I think this solution is the safest!

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it makes sense

nitpick but there should be an empty line at the beginning and at the end of the struct

import SafariServices
import SwiftUI

struct SafariView: UIViewControllerRepresentable {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lmk what you think of this. Seems to work fine and seemed like an easy solution :)

Copy link
Member

Choose a reason for hiding this comment

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

This is good yea! One thing though is that there's an existing (and pretty much identical) SafariView in ButtonComponentView.swift. This gives an "invalid redeclaration" error if PAYWALL_COMPONENTS is enabled.

The only difference between the 2 SafariViews is that the existing one uses

#if canImport(SafariServices)

and the new one uses

#if os(iOS)

Could we consolidate the 2?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ohh I had missed that one! Thanks for the heads up! I can try to consolidate yeah

@@ -425,10 +452,23 @@ extension CustomerCenterConfigData.Localization {

extension CustomerCenterConfigData.HelpPath {

init(from response: CustomerCenterConfigResponse.HelpPath) {
init?(from response: CustomerCenterConfigResponse.HelpPath) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that I changed the constructor to be nullable. If we get a custom url path without a url or open method, we can filter it out and this was the easiest way to achieve that

Copy link
Member

Choose a reason for hiding this comment

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

Would it be possible to make it throw instead? Imo absence of data (null) is not the same as incorrect input data. The latter is an error, the former doesn't have to be.

(I also find it odd that Foundation's URL constructor is nullable, for the same reason.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm so for customer center I would specially take a very "lax" approach. As in, if there is something misconfigured in the backend, I would prefer to try to display as much as possible instead of failing entirely. For example in this case, it basically means that if somehow a misconfiguration happened, we would just hide the path. I think we should do this since we're still changing the schema every now and then and we should be flexible.

Having said that, we could indeed throw in the constructor and handle it when creating the paths. The code becomes a bit less legible IMO, since we need to handle the catch though... I'm not sure TBH, since I know optional constructors are actually quite common in Swift (even if I don't like them as much 😅)... Wdyt @vegaro @aboedo ?

Copy link
Member

Choose a reason for hiding this comment

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

yeah, I think it also kinda maps better to what's happening underneath - you're using an optional constructor to try to build the URL in any case

Copy link
Member

@JayShortway JayShortway Oct 18, 2024

Choose a reason for hiding this comment

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

You're right, this is essentially validating JSON, which I agree should be lax. We could avoid handling the catch and just doing try?:

self.paths = response.paths.compactMap { try? CustomerCenterConfigData.HelpPath(from: $0) }

I don't feel super strongly about this btw, especially since optional constructors are common in Swift, as you said.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm yeah that's not bad. I also don't feel too strongly about this TBH... But considering we don't do that many throws along the codebase, I'm thinking on leaving it as is... If you feel we should better throw an error, I'm ok changing to your suggestion.

Copy link
Member

Choose a reason for hiding this comment

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

No it's minor imo. Feel free to leave as is!

Copy link
Contributor

Choose a reason for hiding this comment

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

I would also leave this as nullable as well because of the optional constructor it is using underneath

@tonidero tonidero marked this pull request as ready for review October 18, 2024 11:43
@tonidero tonidero requested a review from a team October 18, 2024 11:43
Copy link
Member

@JayShortway JayShortway left a comment

Choose a reason for hiding this comment

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

This is gonna be good! The only thing is the duplicate SafariView, and had some other minor comments.

import SafariServices
import SwiftUI

struct SafariView: UIViewControllerRepresentable {
Copy link
Member

Choose a reason for hiding this comment

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

This is good yea! One thing though is that there's an existing (and pretty much identical) SafariView in ButtonComponentView.swift. This gives an "invalid redeclaration" error if PAYWALL_COMPONENTS is enabled.

The only difference between the 2 SafariViews is that the existing one uses

#if canImport(SafariServices)

and the new one uses

#if os(iOS)

Could we consolidate the 2?

#endif

}

struct IdentifiableURL: Identifiable {
Copy link
Member

Choose a reason for hiding this comment

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

Are you getting the warning that it will not behave correctly if URL ever becomes Identifiable in the future? If so, yea I think this solution is the safest!

Comment on lines +234 to +250
public enum OpenMethod: String {

case inApp = "IN_APP"
case external = "EXTERNAL"

init?(from rawValue: String?) {
switch rawValue {
case "IN_APP":
self = .inApp
case "EXTERNAL":
self = .external
default:
return nil
}
}

}
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should consolidate this with the existing PaywallComponent.ButtonComponent.URLMethod. On the one hand I'd say yes, because it is exactly the same concept we're describing so having 2 enums is confusing. On the other hand you could say that we're tying things together which might reduce flexibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm right kinda same thinking as you here... It's internal APIs so I guess we can go either way... I would suggest keeping it separate for flexibility for now, and we can always refactor later if needed. For example, we are currently only adding inApp and external for customer center but we have a deeplink option for paywalls... Having said that, I think we could try to refactor everything into a "link handler" to be used by both paywalls and customer center since it's close to the same thing

@@ -425,10 +452,23 @@ extension CustomerCenterConfigData.Localization {

extension CustomerCenterConfigData.HelpPath {

init(from response: CustomerCenterConfigResponse.HelpPath) {
init?(from response: CustomerCenterConfigResponse.HelpPath) {
Copy link
Member

Choose a reason for hiding this comment

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

Would it be possible to make it throw instead? Imo absence of data (null) is not the same as incorrect input data. The latter is an error, the former doesn't have to be.

(I also find it odd that Foundation's URL constructor is nullable, for the same reason.)

Copy link
Member

@aboedo aboedo left a comment

Choose a reason for hiding this comment

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

awesome!

@@ -186,6 +200,18 @@ private extension ManageSubscriptionsViewModel {
} catch {
self.state = .error(error)
}
case .customUrl:
Copy link
Member

Choose a reason for hiding this comment

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

interesting... It might be worth figuring out a way for those to work eventually since for those it might make even more sense - you can direct users to where you'd actually want them to manage

@@ -425,10 +452,23 @@ extension CustomerCenterConfigData.Localization {

extension CustomerCenterConfigData.HelpPath {

init(from response: CustomerCenterConfigResponse.HelpPath) {
init?(from response: CustomerCenterConfigResponse.HelpPath) {
Copy link
Member

Choose a reason for hiding this comment

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

yeah, I think it also kinda maps better to what's happening underneath - you're using an optional constructor to try to build the URL in any case

Copy link
Member

@JayShortway JayShortway left a comment

Choose a reason for hiding this comment

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

Nice one!

@tonidero
Copy link
Contributor Author

@RCGitBot please test

#endif

}

struct IdentifiableURL: Identifiable {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it makes sense

nitpick but there should be an empty line at the beginning and at the end of the struct

@@ -186,6 +200,18 @@ private extension ManageSubscriptionsViewModel {
} catch {
self.state = .error(error)
}
case .customUrl:
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree we need to do that refactor yes

@@ -425,10 +452,23 @@ extension CustomerCenterConfigData.Localization {

extension CustomerCenterConfigData.HelpPath {

init(from response: CustomerCenterConfigResponse.HelpPath) {
init?(from response: CustomerCenterConfigResponse.HelpPath) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would also leave this as nullable as well because of the optional constructor it is using underneath

@tonidero tonidero force-pushed the sdk-3601-customercentersdk-support-custom-url-paths branch from 9f127de to 487472c Compare October 21, 2024 12:53
@@ -167,4 +179,10 @@ extension CustomerCenterConfigResponse.HelpPath.PathType: CodableEnumWithUnknown

}

extension CustomerCenterConfigResponse.HelpPath.OpenMethod: CodableEnumWithUnknownCase {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@vegaro Note that I added this for the new enum

@tonidero tonidero enabled auto-merge (squash) October 21, 2024 13:12
@tonidero tonidero merged commit 95aaf74 into main Oct 21, 2024
7 checks passed
@tonidero tonidero deleted the sdk-3601-customercentersdk-support-custom-url-paths branch October 21, 2024 13:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants