Files
honeyDueKMP/docs/ANDROID_SUBSCRIPTION_PLAN.md
Trey t 1e2adf7660 Rebrand from Casera/MyCrib to honeyDue
Total rebrand across KMM project:
- Kotlin package: com.example.casera -> com.tt.honeyDue (dirs + declarations)
- Gradle: rootProject.name, namespace, applicationId
- Android: manifest, strings.xml (all languages), widget resources
- iOS: pbxproj bundle IDs, Info.plist, entitlements, xcconfig
- iOS directories: Casera/ -> HoneyDue/, CaseraTests/ -> HoneyDueTests/, etc.
- Swift source: all class/struct/enum renames
- Deep links: casera:// -> honeydue://, .casera -> .honeydue
- App icons replaced with honeyDue honeycomb icon
- Domains: casera.treytartt.com -> honeyDue.treytartt.com
- Bundle IDs: com.tt.casera -> com.tt.honeyDue
- Database table names preserved

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 06:33:57 -06:00

8.4 KiB

Android Subscription & Upgrade UI Parity Plan

Goal

Bring Android subscription/upgrade functionality and UX to match iOS implementation:

  1. Show full inline paywall (not teaser + dialog)
  2. Implement Google Play Billing integration
  3. Disable FAB when upgrade screen is showing

Current State

iOS (Reference)

  • UpgradeFeatureView shows full inline paywall with:
    • Promo content card with feature bullets
    • Subscription product buttons with real pricing
    • Purchase flow via StoreKit 2
    • "Compare Free vs Pro" and "Restore Purchases" links
  • Add button disabled/grayed when upgrade showing
  • StoreKitManager fully implemented

Android (Current)

  • UpgradeFeatureScreen shows simple teaser → opens UpgradePromptDialog
  • FAB always enabled
  • BillingManager is a stub (no real billing)
  • No Google Play Billing dependency

Implementation Plan

Phase 1: Add Google Play Billing Dependency

Files to modify:

  • gradle/libs.versions.toml - Add billing library version
  • composeApp/build.gradle.kts - Add dependency to androidMain
# libs.versions.toml
billing = "7.1.1"

[libraries]
google-billing = { module = "com.android.billingclient:billing-ktx", version.ref = "billing" }
# build.gradle.kts - androidMain.dependencies
implementation(libs.google.billing)

Phase 2: Implement BillingManager

File: composeApp/src/androidMain/kotlin/com/example/honeydue/platform/BillingManager.kt

Replace stub implementation with full Google Play Billing:

class BillingManager private constructor(private val context: Context) {
    // Product IDs (match Google Play Console)
    private val productIDs = listOf(
        "com.tt.honeyDue.pro.monthly",
        "com.tt.honeyDue.pro.annual"
    )

    // BillingClient instance
    private var billingClient: BillingClient

    // StateFlows for UI
    val products = MutableStateFlow<List<ProductDetails>>(emptyList())
    val purchasedProductIDs = MutableStateFlow<Set<String>>(emptySet())
    val isLoading = MutableStateFlow(false)
    val purchaseError = MutableStateFlow<String?>(null)

    // Key methods to implement:
    - startConnection() - Connect to Google Play
    - loadProducts() - Query subscription products
    - purchase(activity, productDetails) - Launch purchase flow
    - restorePurchases() - Query purchase history
    - verifyPurchaseWithBackend() - Call SubscriptionApi.verifyAndroidPurchase()
    - acknowledgePurchase() - Required by Google
    - listenForPurchases() - PurchasesUpdatedListener

Key implementation details:

  1. Initialize BillingClient with PurchasesUpdatedListener
  2. Handle billing connection state (retry on disconnect)
  3. Query products using QueryProductDetailsParams with ProductType.SUBS
  4. Launch purchase flow with BillingFlowParams
  5. Process purchase results and verify with backend
  6. Acknowledge purchases (required or they refund after 3 days)
  7. Update SubscriptionCache after successful verification

Phase 3: Update UpgradeFeatureScreen

File: composeApp/src/commonMain/kotlin/com/example/honeydue/ui/subscription/UpgradeFeatureScreen.kt

Transform from teaser+dialog to full inline paywall matching iOS:

Current structure:

  • Icon, title, message, badge
  • Button opens UpgradePromptDialog

New structure:

@Composable
fun UpgradeFeatureScreen(
    triggerKey: String,
    icon: ImageVector,
    onNavigateBack: () -> Unit,
    billingManager: BillingManager? = null  // Android-only, null on other platforms
) {
    // ScrollView with:
    // 1. Star icon (accent gradient)
    // 2. Title + message from triggerData
    // 3. PromoContentCard - feature bullets from triggerData.promoHtml
    // 4. SubscriptionProductButtons - show real products with pricing
    // 5. "Compare Free vs Pro" button
    // 6. "Restore Purchases" button
    // 7. Error display if any
}

New components to add:

  • PromoContentCard - Parse and display promo HTML as composable
  • SubscriptionProductButton - Display product with name, price, optional savings badge

Phase 4: Create Android-Specific Product Display

File: composeApp/src/androidMain/kotlin/com/example/honeydue/ui/subscription/SubscriptionProductButton.kt

@Composable
fun SubscriptionProductButton(
    productDetails: ProductDetails,
    isSelected: Boolean,
    isProcessing: Boolean,
    onSelect: () -> Unit
) {
    // Display:
    // - Product name (e.g., "HoneyDue Pro Monthly")
    // - Price from subscriptionOfferDetails
    // - "Save X%" badge for annual
    // - Loading indicator when processing
}

Helper function for savings calculation:

fun calculateAnnualSavings(monthly: ProductDetails, annual: ProductDetails): Int?

Phase 5: Disable FAB When Upgrade Showing

Files to modify:

  • composeApp/src/commonMain/kotlin/com/example/honeydue/ui/screens/ContractorsScreen.kt
  • composeApp/src/commonMain/kotlin/com/example/honeydue/ui/screens/DocumentsScreen.kt

Changes:

// In ContractorsScreen
val shouldShowUpgradePrompt = SubscriptionHelper.shouldShowUpgradePromptForContractors().allowed

// Update FAB
FloatingActionButton(
    onClick = { if (!shouldShowUpgradePrompt) showAddDialog = true },
    containerColor = if (shouldShowUpgradePrompt)
        MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f)
    else
        MaterialTheme.colorScheme.primary,
    contentColor = if (shouldShowUpgradePrompt)
        MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
    else
        MaterialTheme.colorScheme.onPrimary
) {
    Icon(Icons.Default.Add, "Add contractor")
}
// Add .alpha() modifier or enabled state

Same pattern for DocumentsScreen.


Phase 6: Initialize BillingManager in MainActivity

File: composeApp/src/androidMain/kotlin/com/example/honeydue/MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Existing initializations...
    TokenStorage.initialize(...)
    ThemeStorage.initialize(...)
    ThemeManager.initialize()

    // Add BillingManager initialization
    val billingManager = BillingManager.getInstance(applicationContext)
    billingManager.startConnection(
        onSuccess = { billingManager.loadProducts() },
        onError = { error -> Log.e("Billing", "Connection failed: $error") }
    )

    setContent {
        // Pass billingManager to composables that need it
        App(billingManager = billingManager)
    }
}

Phase 7: Wire Purchase Flow End-to-End

Integration points:

  1. UpgradeFeatureScreen observes BillingManager.products StateFlow
  2. User taps product → calls BillingManager.purchase(activity, productDetails)
  3. BillingManager launches Google Play purchase UI
  4. On success → calls SubscriptionApi.verifyAndroidPurchase()
  5. Backend verifies with Google → updates user's subscription tier
  6. BillingManager calls SubscriptionApi.getSubscriptionStatus()
  7. Updates SubscriptionCache with new status
  8. UI recomposes, upgrade screen disappears, FAB becomes enabled

Files Summary

File Action
gradle/libs.versions.toml Add billing version
composeApp/build.gradle.kts Add billing dependency
BillingManager.kt Full rewrite with real billing
UpgradeFeatureScreen.kt Transform to inline paywall
ContractorsScreen.kt Disable FAB when upgrade showing
DocumentsScreen.kt Disable FAB when upgrade showing
MainActivity.kt Initialize BillingManager

Reference Files (iOS Implementation)

These files show the iOS implementation to mirror:

  • iosApp/iosApp/Subscription/StoreKitManager.swift - Full billing manager
  • iosApp/iosApp/Subscription/UpgradeFeatureView.swift - Inline paywall UI
  • iosApp/iosApp/Subscription/UpgradePromptView.swift - PromoContentView, SubscriptionProductButton

Testing Checklist

  • Products load from Google Play Console
  • Purchase flow launches correctly
  • Purchase verification with backend works
  • SubscriptionCache updates after purchase
  • FAB disabled when upgrade prompt showing
  • FAB enabled after successful purchase
  • Restore purchases works
  • Error states display correctly

Notes

  • Product IDs must match Google Play Console: com.tt.honeyDue.pro.monthly, com.tt.honeyDue.pro.annual
  • Backend endpoint POST /subscription/verify-android/ already exists in SubscriptionApi
  • Testing requires Google Play Console setup with test products
  • Use Google's test cards for sandbox testing