diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 0fd2617ca..411b3ba42 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -144,6 +144,7 @@ 2C2D73442B1736E000CBB1DA /* AppTabItemsAvailabilityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2D73432B1736E000CBB1DA /* AppTabItemsAvailabilityService.swift */; }; 2C2ECCA5288C0661008DDCBA /* StepQuizRetryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2ECCA4288C0661008DDCBA /* StepQuizRetryButton.swift */; }; 2C2ECCA7288C0BF7008DDCBA /* View+ConditionalViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2ECCA6288C0BF7008DDCBA /* View+ConditionalViewModifier.swift */; }; + 2C2F7CFB2C94023100C300B9 /* PaywallSubscriptionProductsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2F7CFA2C94023100C300B9 /* PaywallSubscriptionProductsView.swift */; }; 2C2FD61E28191EC0004E7AF6 /* SentryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD61D28191EC0004E7AF6 /* SentryManager.swift */; }; 2C2FD62028191FFE004E7AF6 /* Sentry-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C2FD61F28191FFE004E7AF6 /* Sentry-Info.plist */; }; 2C2FD622281920B1004E7AF6 /* SentryInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD621281920B1004E7AF6 /* SentryInfo.swift */; }; @@ -946,6 +947,7 @@ 2C2D73432B1736E000CBB1DA /* AppTabItemsAvailabilityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTabItemsAvailabilityService.swift; sourceTree = ""; }; 2C2ECCA4288C0661008DDCBA /* StepQuizRetryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizRetryButton.swift; sourceTree = ""; }; 2C2ECCA6288C0BF7008DDCBA /* View+ConditionalViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+ConditionalViewModifier.swift"; sourceTree = ""; }; + 2C2F7CFA2C94023100C300B9 /* PaywallSubscriptionProductsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallSubscriptionProductsView.swift; sourceTree = ""; }; 2C2FD61D28191EC0004E7AF6 /* SentryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryManager.swift; sourceTree = ""; }; 2C2FD61F28191FFE004E7AF6 /* Sentry-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Sentry-Info.plist"; sourceTree = ""; }; 2C2FD621281920B1004E7AF6 /* SentryInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryInfo.swift; sourceTree = ""; }; @@ -2539,6 +2541,7 @@ 2C9320F42B68F14100999992 /* PaywallContentView.swift */, 2C7C0D622B6B45A20093609D /* PaywallFeaturesView.swift */, 2C7271272B6B92AD005628B0 /* PaywallFooterView.swift */, + 2C2F7CFA2C94023100C300B9 /* PaywallSubscriptionProductsView.swift */, ); path = Content; sourceTree = ""; @@ -5528,6 +5531,7 @@ E9FB89AC2893EA580011EFFB /* NotificationPermissionStatus.swift in Sources */, 2C4FBD8C2876C39C00ACA5C8 /* ProfileAboutView.swift in Sources */, 2C023C88285D928100D2D5A9 /* StepQuizTableViewModel.swift in Sources */, + 2C2F7CFB2C94023100C300B9 /* PaywallSubscriptionProductsView.swift in Sources */, E99B21872887E9C5006A6154 /* StepQuizSortingSkeletonView.swift in Sources */, E98BE36D2A374394000B430F /* StreakRecoveryModalView.swift in Sources */, 2CAE8D0C2805829A00E6C83D /* StepViewData.swift in Sources */, diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift index 8210c0054..20e8bb922 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift @@ -646,6 +646,9 @@ enum Strings { static let subscriptionFeature1 = sharedStrings.mobile_only_subscription_feature_1.localized() static let subscriptionFeature2 = sharedStrings.mobile_only_subscription_feature_2.localized() static let subscriptionFeature3 = sharedStrings.mobile_only_subscription_feature_3.localized() + static let subscriptionFeature4 = sharedStrings.mobile_only_subscription_feature_4.localized() + + static let bestValueBadge = sharedStrings.paywall_best_value_label.localized() } // MARK: - ManageSubscription - diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/PaywallViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/PaywallViewModel.swift index ce34696c9..f083c0b27 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/PaywallViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/PaywallViewModel.swift @@ -6,6 +6,8 @@ final class PaywallViewModel: FeatureViewModel< PaywallFeatureMessage, PaywallFeatureActionViewAction > { + private let selectionFeedbackGenerator = FeedbackGenerator(feedbackType: .selection) + var contentStateKs: PaywallFeatureViewStateContentKs { .init(state.contentState) } init(feature: Presentation_reduxFeature) { @@ -33,6 +35,12 @@ final class PaywallViewModel: FeatureViewModel< onNewMessage(PaywallFeatureMessageRetryContentLoading()) } + @MainActor + func doSubscriptionProductAction(product: PaywallFeatureViewStateContentSubscriptionProduct) { + selectionFeedbackGenerator.triggerFeedback() + onNewMessage(PaywallFeatureMessageProductClicked(productId: product.productId)) + } + func doBuySubscription() { onNewMessage(PaywallFeatureMessageBuySubscriptionClicked(purchaseParams: PlatformPurchaseParams())) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallContentView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallContentView.swift index f4ce0b73c..30565a7a3 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallContentView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallContentView.swift @@ -1,3 +1,4 @@ +import shared import SwiftUI extension PaywallContentView { @@ -14,9 +15,10 @@ extension PaywallContentView { struct PaywallContentView: View { private(set) var appearance = Appearance() + let subscriptionProducts: [PaywallFeatureViewStateContentSubscriptionProduct] let buyButtonText: String - let buyFootnoteText: String? + let onSubscriptionProductTap: (PaywallFeatureViewStateContentSubscriptionProduct) -> Void let onBuyButtonTap: () -> Void let onTermsOfServiceButtonTap: () -> Void @@ -50,6 +52,13 @@ struct PaywallContentView: View { PaywallFeaturesView( appearance: .init(spacing: appearance.interitemSpacing) ) + + PaywallSubscriptionProductsView( + appearance: .init(spacing: appearance.interitemSpacing), + subscriptionProducts: subscriptionProducts, + onTap: onSubscriptionProductTap + ) + .padding(.top) } .padding(appearance.padding) } @@ -57,7 +66,6 @@ struct PaywallContentView: View { PaywallFooterView( appearance: .init(spacing: appearance.interitemSpacing), buyButtonText: buyButtonText, - buyFootnoteText: buyFootnoteText, onBuyButtonTap: onBuyButtonTap, onTermsOfServiceButtonTap: onTermsOfServiceButtonTap ) @@ -68,17 +76,24 @@ struct PaywallContentView: View { #if DEBUG #Preview { PaywallContentView( - buyButtonText: "Subscribe for $11.99/month", - buyFootnoteText: nil, - onBuyButtonTap: {}, - onTermsOfServiceButtonTap: {} - ) -} - -#Preview { - PaywallContentView( - buyButtonText: "Subscribe for $11.99/month", - buyFootnoteText: "Then $11.99 per month", + subscriptionProducts: [ + .init( + productId: "1", + title: "Monthly Subscription", + subtitle: "$11.99 / month", + isBestValue: false, + isSelected: false + ), + .init( + productId: "2", + title: "Yearly Subscription", + subtitle: "$99.99 / year", + isBestValue: true, + isSelected: true + ) + ], + buyButtonText: "Start now", + onSubscriptionProductTap: { _ in }, onBuyButtonTap: {}, onTermsOfServiceButtonTap: {} ) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallFeaturesView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallFeaturesView.swift index e1718fbf1..434d368ff 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallFeaturesView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallFeaturesView.swift @@ -10,7 +10,8 @@ struct PaywallFeaturesView: View { private static let features = [ Strings.Paywall.subscriptionFeature1, Strings.Paywall.subscriptionFeature2, - Strings.Paywall.subscriptionFeature3 + Strings.Paywall.subscriptionFeature3, + Strings.Paywall.subscriptionFeature4 ] private(set) var appearance = Appearance() @@ -42,6 +43,7 @@ private struct PaywallFeatureView: View { Label( title: { Text(title) + .foregroundColor(.newPrimaryText) .offset(x: !animateTitle ? -width : 0) .clipped() }, diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallFooterView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallFooterView.swift index 2163b0d0e..a0a368eb7 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallFooterView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallFooterView.swift @@ -3,7 +3,6 @@ import SwiftUI extension PaywallFooterView { struct Appearance { var spacing = LayoutInsets.defaultInset - var interitemSpacing = LayoutInsets.smallInset } } @@ -11,7 +10,6 @@ struct PaywallFooterView: View { private(set) var appearance = Appearance() let buyButtonText: String - let buyFootnoteText: String? let onBuyButtonTap: () -> Void let onTermsOfServiceButtonTap: () -> Void @@ -30,23 +28,15 @@ struct PaywallFooterView: View { @MainActor private var content: some View { VStack(alignment: .center, spacing: appearance.spacing) { - VStack(alignment: .center, spacing: appearance.interitemSpacing) { - Button( - buyButtonText, - action: { - feedbackGenerator.triggerFeedback() - onBuyButtonTap() - } - ) - .buttonStyle(.primary) - .shineEffect() - - if let buyFootnoteText { - Text(buyFootnoteText) - .font(.footnote.bold()) - .foregroundColor(.newSecondaryText) + Button( + buyButtonText, + action: { + feedbackGenerator.triggerFeedback() + onBuyButtonTap() } - } + ) + .buttonStyle(.primary) + .shineEffect() Button( Strings.Paywall.termsOfServiceButton, @@ -63,20 +53,10 @@ struct PaywallFooterView: View { #if DEBUG #Preview { - VStack { - PaywallFooterView( - buyButtonText: "Subscribe for $11.99/month", - buyFootnoteText: nil, - onBuyButtonTap: {}, - onTermsOfServiceButtonTap: {} - ) - - PaywallFooterView( - buyButtonText: "Subscribe for $11.99/month", - buyFootnoteText: "Then $11.99 per month", - onBuyButtonTap: {}, - onTermsOfServiceButtonTap: {} - ) - } + PaywallFooterView( + buyButtonText: "Subscribe for $11.99/month", + onBuyButtonTap: {}, + onTermsOfServiceButtonTap: {} + ) } #endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallSubscriptionProductsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallSubscriptionProductsView.swift new file mode 100644 index 000000000..afbeefe8c --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/Content/PaywallSubscriptionProductsView.swift @@ -0,0 +1,120 @@ +import shared +import SwiftUI + +extension PaywallSubscriptionProductsView { + struct Appearance { + var spacing = LayoutInsets.smallInset + + let padding = LayoutInsets.defaultInset + + let badgeInsets = LayoutInsets(horizontal: 8, vertical: 4) + let badgeFont = UIFont.preferredFont(forTextStyle: .footnote) + + func badgeTopOffset() -> CGFloat { + badgeFont.pointSize / 2.0 + badgeInsets.top + } + } +} + +struct PaywallSubscriptionProductsView: View { + private(set) var appearance = Appearance() + + let subscriptionProducts: [PaywallFeatureViewStateContentSubscriptionProduct] + + let onTap: (PaywallFeatureViewStateContentSubscriptionProduct) -> Void + + var body: some View { + VStack(alignment: .center, spacing: appearance.spacing) { + ForEach( + Array(subscriptionProducts.enumerated()), + id: \.element.productId + ) { index, product in + buildProductView( + product: product, + action: { + onTap(product) + } + ) + .padding(.top, product.isBestValue && index > 0 ? appearance.spacing : 0) + } + } + } + + private var bestValueBadgeView: some View { + Text(Strings.Paywall.bestValueBadge) + .font(Font(appearance.badgeFont)) + .foregroundColor(Color(ColorPalette.onPrimary)) + .padding(appearance.badgeInsets.edgeInsets) + .background(Color(ColorPalette.primary)) + .clipShape(Capsule()) + .fixedSize() + } + + private func buildProductView( + product: PaywallFeatureViewStateContentSubscriptionProduct, + action: @escaping () -> Void + ) -> some View { + Button( + action: action, + label: { + HStack(alignment: .center, spacing: 0) { + Text(product.title) + .font(.body.bold()) + + Spacer() + + Text(product.subtitle) + .font(.body) + } + .foregroundColor(.newPrimaryText) + .padding(.horizontal, appearance.padding) + .padding(.vertical, product.isSelected ? appearance.padding * 2 : appearance.padding) + .conditionalOpacity(isEnabled: product.isSelected) + .addBorder( + color: product.isSelected ? Color(ColorPalette.primary) : .border, + width: product.isSelected ? 2 : 1 + ) + .animation(.default, value: product.isSelected) + .overlay( + bestValueBadgeView + .opacity(product.isBestValue ? 1 : 0) + .alignmentGuide(.top, computeValue: { dimension in + dimension[.top] + appearance.badgeTopOffset() + }) + .alignmentGuide(.trailing, computeValue: { dimension in + dimension[.trailing] - appearance.badgeInsets.trailing + }) + , + alignment: .init(horizontal: .trailing, vertical: .top) + ) + } + ) + } +} + +#if DEBUG +#Preview { + VStack { + PaywallSubscriptionProductsView( + subscriptionProducts: [ + .init( + productId: "1", + title: "Monthly Subscription", + subtitle: "$11.99 / month", + isBestValue: false, + isSelected: false + ), + .init( + productId: "2", + title: "Yearly Subscription", + subtitle: "$99.99 / year", + isBestValue: true, + isSelected: true + ) + ], + onTap: { _ in } + ) + } + .padding() +} +#endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/PaywallView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/PaywallView.swift index ece8d6a37..4b0981a2d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/PaywallView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Paywall/Views/PaywallView.swift @@ -53,8 +53,9 @@ struct PaywallView: View { ) case .content(let content): PaywallContentView( + subscriptionProducts: content.subscriptionProducts, buyButtonText: content.buyButtonText, - buyFootnoteText: content.trialText, + onSubscriptionProductTap: viewModel.doSubscriptionProductAction(product:), onBuyButtonTap: viewModel.doBuySubscription, onTermsOfServiceButtonTap: viewModel.doTermsOfServicePresentation ) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Systems/PurchaseManager.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Systems/PurchaseManager.swift index 3463ce856..53d68a0e9 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Systems/PurchaseManager.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Systems/PurchaseManager.swift @@ -95,7 +95,9 @@ created: \(created) #if DEBUG print(""" -PurchaseManager: purchase \(storeProduct.productIdentifier) failed, error: \(error), purchaseResult: \(purchaseResult) +PurchaseManager: purchase \(storeProduct.productIdentifier) failed, \ +error: \(error), \ +purchaseResult: \(purchaseResult) """) #endif @@ -109,7 +111,9 @@ PurchaseManager: purchase \(storeProduct.productIdentifier) failed, error: \(err if let storeTransaction, let customerInfo { #if DEBUG print(""" -PurchaseManager: purchase \(storeProduct.productIdentifier) succeeded, storeTransaction: \(storeTransaction), customerInfo: \(customerInfo) +PurchaseManager: purchase \(storeProduct.productIdentifier) succeeded, \ +storeTransaction: \(storeTransaction), \ +customerInfo: \(customerInfo) """) #endif @@ -124,7 +128,9 @@ PurchaseManager: purchase \(storeProduct.productIdentifier) succeeded, storeTran completionHandler(result, nil) } else { #if DEBUG - print("PurchaseManager: purchase \(storeProduct.productIdentifier) failed, no storeTransaction or customerInfo") + print(""" +PurchaseManager: purchase \(storeProduct.productIdentifier) failed, no storeTransaction or customerInfo +""") #endif let purchaseResult = PurchaseResultErrorOtherError( @@ -174,22 +180,6 @@ PurchaseManager: get management URL succeeded, managementURL: \(String(describin } } } - - func checkTrialOrIntroDiscountEligibility( - productId: String, - completionHandler: @escaping (KotlinBoolean?, (any Error)?) -> Void - ) { - Purchases.shared.checkTrialOrIntroDiscountEligibility( - productIdentifiers: [productId] - ) { eligibilities in - if let eligibility = eligibilities[productId] { - let isEligible = eligibility.status == .eligible - completionHandler(KotlinBoolean(value: isEligible), nil) - } else { - completionHandler(KotlinBoolean(value: false), nil) - } - } - } } // MARK: - RevenueCat.PublicError (shared.PurchaseResult) - diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/purchases/domain/AndroidPurchaseManager.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/purchases/domain/AndroidPurchaseManager.kt index 00305c080..067692178 100644 --- a/shared/src/androidMain/kotlin/org/hyperskill/app/purchases/domain/AndroidPurchaseManager.kt +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/purchases/domain/AndroidPurchaseManager.kt @@ -124,6 +124,4 @@ class AndroidPurchaseManager( ) } } - - override suspend fun checkTrialEligibility(productId: String): Boolean = false } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallActionDispatcher.kt index b7c86f9d2..b8044688f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallActionDispatcher.kt @@ -1,8 +1,6 @@ package org.hyperskill.app.paywall.presentation import co.touchlab.kermit.Logger -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.paywall.presentation.PaywallFeature.Action import org.hyperskill.app.paywall.presentation.PaywallFeature.InternalAction @@ -49,19 +47,15 @@ internal class PaywallActionDispatcher( InternalMessage.FetchSubscriptionProductsError } ) { - coroutineScope { - val subscriptionProductsDeferred = async { - purchaseInteractor.getSubscriptionProducts() - } - - val subscriptionProducts = subscriptionProductsDeferred.await().getOrThrow() + val subscriptionProducts = purchaseInteractor + .getSubscriptionProducts() + .getOrThrow() - if (subscriptionProducts.isNotEmpty()) { - InternalMessage.FetchSubscriptionProductsSuccess(subscriptionProducts) - } else { - logger.e { "Receive null instead of formatted mobile-only subscription price" } - InternalMessage.FetchSubscriptionProductsError - } + if (subscriptionProducts.isNotEmpty()) { + InternalMessage.FetchSubscriptionProductsSuccess(subscriptionProducts) + } else { + logger.e { "Receive null instead of formatted mobile-only subscription price" } + InternalMessage.FetchSubscriptionProductsError } }.let(onNewMessage) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallFeature.kt index 1d73db48a..99247b434 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallFeature.kt @@ -33,8 +33,7 @@ object PaywallFeature { data object Error : ViewStateContent data class Content( val subscriptionProducts: List, - val buyButtonText: String, - val trialText: String? = null + val buyButtonText: String ) : ViewStateContent data object SubscriptionSyncLoading : ViewStateContent diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/view/PaywallViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/view/PaywallViewStateMapper.kt index 2157ee5af..449fbddbf 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/view/PaywallViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/view/PaywallViewStateMapper.kt @@ -16,8 +16,7 @@ import org.hyperskill.app.purchases.domain.model.SubscriptionPeriod import org.hyperskill.app.purchases.domain.model.SubscriptionProduct internal class PaywallViewStateMapper( - private val resourceProvider: ResourceProvider/*, - private val platformType: PlatformType*/ + private val resourceProvider: ResourceProvider ) { fun map( state: State, @@ -53,15 +52,7 @@ internal class PaywallViewStateMapper( product = product, isSelected = product.id == state.selectedProductId ) - }, - trialText = null/*if (platformType == PlatformType.IOS && state.isTrialEligible) { - resourceProvider.getString( - SharedResources.strings.paywall_ios_mobile_only_trial_description, - state.formattedPrice - ) - } else { - null - }*/ + } ) private fun mapSubscriptionProductToSubscriptionOption( @@ -87,16 +78,4 @@ internal class PaywallViewStateMapper( isBestValue = index == 0, isSelected = isSelected ) - - /*private fun getBuyButtonText(state: State.Content): String = - when (platformType) { - PlatformType.IOS -> - if (state.isTrialEligible) { - resourceProvider.getString(SharedResources.strings.paywall_ios_mobile_only_trial_buy_btn) - } else { - resourceProvider.getString(SharedResources.strings.paywall_ios_mobile_only_buy_btn) - } - PlatformType.ANDROID -> - resourceProvider.getString(SharedResources.strings.paywall_subscription_start_btn) - }*/ } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/interactor/PurchaseInteractor.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/interactor/PurchaseInteractor.kt index 4dad641fe..e00728bbd 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/interactor/PurchaseInteractor.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/interactor/PurchaseInteractor.kt @@ -3,7 +3,6 @@ package org.hyperskill.app.purchases.domain.interactor import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor import org.hyperskill.app.analytic.domain.model.AnalyticKeys import org.hyperskill.app.purchases.domain.model.HyperskillStoreProduct -import org.hyperskill.app.purchases.domain.model.PlatformProductIdentifiers import org.hyperskill.app.purchases.domain.model.PlatformPurchaseParams import org.hyperskill.app.purchases.domain.model.PurchaseManager import org.hyperskill.app.purchases.domain.model.PurchaseResult @@ -49,9 +48,6 @@ class PurchaseInteractor( suspend fun getSubscriptionProducts(): Result> = purchaseManager.getSubscriptionProducts() - suspend fun checkTrialEligibilityForMobileOnlySubscription(): Boolean = - purchaseManager.checkTrialEligibility(PlatformProductIdentifiers.MOBILE_ONLY_MONTHLY_SUBSCRIPTION) - suspend fun getManagementUrl(): Result = purchaseManager.getManagementUrl() } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PurchaseManager.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PurchaseManager.kt index 96ae6fbd7..20e1ccc42 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PurchaseManager.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/purchases/domain/model/PurchaseManager.kt @@ -31,9 +31,4 @@ interface PurchaseManager { suspend fun getManagementUrl(): Result suspend fun getSubscriptionProducts(): Result> - - /** - * Checks if user is eligible for trial for the product with [productId] - */ - suspend fun checkTrialEligibility(productId: String): Boolean } \ No newline at end of file diff --git a/shared/src/commonMain/moko-resources/base/strings.xml b/shared/src/commonMain/moko-resources/base/strings.xml index e6868da18..d6fdb269b 100644 --- a/shared/src/commonMain/moko-resources/base/strings.xml +++ b/shared/src/commonMain/moko-resources/base/strings.xml @@ -696,10 +696,6 @@ Get the full experience - Subscribe for %s / month - Start with a 1 week free trial - Then %s per month - Subscribe Oops! We were unable to load the subscriptions data. Subscription Purchase failed. Please try again. @@ -708,7 +704,6 @@ The app is updating. Please wait a moment. Hyperskill Terms of Service and Privacy Policy https://hi.hyperskill.org/terms - Monthly Monthly Annual %s %s / month diff --git a/shared/src/iosMain/kotlin/org/hyperskill/app/purchases/domain/manager/IosPurchaseManager.kt b/shared/src/iosMain/kotlin/org/hyperskill/app/purchases/domain/manager/IosPurchaseManager.kt index cccd77584..0523bd27e 100644 --- a/shared/src/iosMain/kotlin/org/hyperskill/app/purchases/domain/manager/IosPurchaseManager.kt +++ b/shared/src/iosMain/kotlin/org/hyperskill/app/purchases/domain/manager/IosPurchaseManager.kt @@ -18,6 +18,4 @@ interface IosPurchaseManager { ): SwiftyResult suspend fun getManagementUrl(): SwiftyResult - - suspend fun checkTrialOrIntroDiscountEligibility(productId: String): Boolean } \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/org/hyperskill/app/purchases/domain/manager/IosPurchaseManagerImpl.kt b/shared/src/iosMain/kotlin/org/hyperskill/app/purchases/domain/manager/IosPurchaseManagerImpl.kt index 97d3af839..13f77f41a 100644 --- a/shared/src/iosMain/kotlin/org/hyperskill/app/purchases/domain/manager/IosPurchaseManagerImpl.kt +++ b/shared/src/iosMain/kotlin/org/hyperskill/app/purchases/domain/manager/IosPurchaseManagerImpl.kt @@ -66,14 +66,13 @@ internal class IosPurchaseManagerImpl( } } - @Suppress("VariableNaming") private fun mapOfferingsToSubscriptionProducts(rcOfferings: RCOfferings): List { val currentOffering = rcOfferings.current() ?: return emptyList() return currentOffering .availablePackages() .mapNotNull { - val _package = it as? RCPackage ?: return@mapNotNull null - val rcStoreProduct = _package.storeProduct() + val rcPackage = it as? RCPackage ?: return@mapNotNull null + val rcStoreProduct = rcPackage.storeProduct() SubscriptionProduct( id = rcStoreProduct.productIdentifier(), period = when (rcStoreProduct.subscriptionPeriod()?.unit()) { @@ -87,9 +86,6 @@ internal class IosPurchaseManagerImpl( ) } } - - override suspend fun checkTrialEligibility(productId: String): Boolean = - purchases.checkTrialOrIntroDiscountEligibility(productId) } class FetchOfferingsException(override val message: String?) : Exception() \ No newline at end of file