-
Notifications
You must be signed in to change notification settings - Fork 338
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
Support custom URL paths in ManageSubscriptionsView
#4382
Conversation
@@ -186,6 +200,18 @@ private extension ManageSubscriptionsViewModel { | |||
} catch { | |||
self.state = .error(error) | |||
} | |||
case .customUrl: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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 SafariView
s is that the existing one uses
#if canImport(SafariServices)
and the new one uses
#if os(iOS)
Could we consolidate the 2?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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 SafariView
s 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 { |
There was a problem hiding this comment.
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!
RevenueCatUI/CustomerCenter/ViewModels/ManageSubscriptionsViewModel.swift
Outdated
Show resolved
Hide resolved
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 | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome!
RevenueCatUI/CustomerCenter/ViewModels/ManageSubscriptionsViewModel.swift
Outdated
Show resolved
Hide resolved
@@ -186,6 +200,18 @@ private extension ManageSubscriptionsViewModel { | |||
} catch { | |||
self.state = .error(error) | |||
} | |||
case .customUrl: |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice one!
@RCGitBot please test |
#endif | ||
|
||
} | ||
|
||
struct IdentifiableURL: Identifiable { |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
9f127de
to
487472c
Compare
@@ -167,4 +179,10 @@ extension CustomerCenterConfigResponse.HelpPath.PathType: CodableEnumWithUnknown | |||
|
|||
} | |||
|
|||
extension CustomerCenterConfigResponse.HelpPath.OpenMethod: CodableEnumWithUnknownCase { |
There was a problem hiding this comment.
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
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