feat(subscription): RevenueCat KMP subscriptions + premium gate on AI chat#440
Open
plusmobileapps wants to merge 6 commits into
Open
feat(subscription): RevenueCat KMP subscriptions + premium gate on AI chat#440plusmobileapps wants to merge 6 commits into
plusmobileapps wants to merge 6 commits into
Conversation
Add the SubscriptionRepository contract (the source of truth for premium status) with its testing fake, and a RevenueCat-backed impl. RevenueCat's purchases-kmp SDK is Android/iOS only, so the gateway + initializer are duplicated across androidMain/iosMain with a JVM no-op (desktop is never premium). Configure the SDK at startup via a SubscriptionInitializer, mirroring BugsnagInitializer, wired from MyApplication (Android) and iOSApp.swift; keys come from BuildKonfig (blank-key-safe no-op). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the SubscriptionBloc + custom Compose paywall (SubscriptionScreen) built from Plus* components: selectable packages, subscribe/restore CTAs, loading/premium/unavailable/error states. The ViewModel drives purchase and restore through SubscriptionRepository and reflects premium status live. Also add the PremiumRequiredDialog used by the AI-chat gate. Includes ViewModel unit tests over the repository fake. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Centralize the AI-chat premium gate in RootBlocImpl: premium users open the chat directly; everyone else gets a "premium required" dialog (rendered at the root) whose confirmation opens the paywall as a root child. All existing AI-chat entry points (bottom nav, recipe detail, cook mode) route through the gate. Add a "ChefMate Premium" row to the More tab that opens the paywall directly, threaded through the Settings -> BottomNav -> Root output chain. Includes RootBloc gate tests and the Settings output test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add public preview blocs (SubscriptionPreviews) and @previewTest screenshot wrappers for the paywall — light/dark, plus loading, premium, unavailable, and error states — with recorded reference images. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add SubscriptionRobot and a MoreRobot entry for the premium row, plus a runRootBlocTest flow covering both paywall entry points: the More-tab row and the AI-chat gate (dialog -> confirm -> paywall). Wire a TestSubscriptionRepository into the test graph (replacing the production impl) so tests can grant premium; the existing AI-chat navigation tests now set premium so they still reach the chat. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RevenueCat's iOS code is Swift, and its object files auto-link the Swift back-compat static libs (swiftCompatibility56, swiftCompatibilityConcurrency, …). Static frameworks are linked by Xcode, which supplies these, but the Kotlin/Native test executables are linked by Gradle outside Xcode and fail with "library 'swiftCompatibility56' not found" (linkDebugTestIosSimulatorArm64). Point those test binaries at the active Xcode toolchain's Swift lib dir for the target SDK. Guarded to macOS so non-Apple CI configuration is unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds the RevenueCat Kotlin Multiplatform SDK and uses a premium entitlement to feature-gate the AI chat.
SubscriptionRepositoryis the source of truth for premium status, with its ownpubliccontract and atestingfake so other features gate/integration-test against it without the production impl.SubscriptionBloc+SubscriptionScreen, built fromPlus*components) — selectable packages, subscribe/restore, and loading/premium/unavailable/error states.RootBlocImpl: non-subscribers get a "premium required" dialog at the root; confirming opens the paywall as a root child. Premium users open the chat directly. Covers every entry point (bottom-nav action, recipe detail, cook mode, More-tab AI row).RevenueCat integration notes
3.3.0(purchases-kmp-core+purchases-kmp-result). The SDK is Android/iOS only, so the gateway + initializer live inandroidMain/iosMainwith a JVM no-op (desktop is never premium — subscriptions are mobile-only). Verified compiling on Android, iOS, and JVM.SubscriptionInitializer(mirrorsBugsnagInitializer), wired fromMyApplicationandiOSApp.swift. API keys come from BuildKonfig (REVENUECAT_ANDROID_API_KEY/REVENUECAT_IOS_API_KEY) and are blank-key-safe — a blank key skips setup.Before this ships
premiumentitlement + an offering, and set the Android/iOS API keys. Without keys the SDK no-ops and the paywall shows its "unavailable" state.Testing
SubscriptionRepositoryImplTest(fake gateway),SubscriptionViewModelTest, andRootBlocTestgate cases (premium → chat; free → dialog → confirm → paywall) + Settings output test.SubscriptionNavigationUiTest). Existing AI-chat nav tests now grant premium via aTestSubscriptionRepositorywired into the test graph.Follow-ups
userCancelledflag isn't yet mapped into common) — small follow-up.SettingsScreenstill has no snapshot coverage (pre-existing gap; its preview blocs are private).🤖 Generated with Claude Code