| .. | .. |
|---|
| 5 | 5 | import 'package:shared_preferences/shared_preferences.dart'; |
|---|
| 6 | 6 | |
|---|
| 7 | 7 | /// Product ID for the one-time full-access purchase. |
|---|
| 8 | | -const String kFullAccessProductId = 'com.tekmidian.pailot.fullaccess'; |
|---|
| 8 | +/// Matches the IAPs registered in App Store Connect (Apple ID 6779190925) |
|---|
| 9 | +/// and Google Play Console (one-time product `com.tekmidian.pailot.pro`). |
|---|
| 10 | +const String kFullAccessProductId = 'com.tekmidian.pailot.pro'; |
|---|
| 9 | 11 | |
|---|
| 10 | 12 | /// Maximum sessions allowed on the free tier. |
|---|
| 11 | 13 | const int kFreeTierMaxSessions = 2; |
|---|
| 12 | 14 | |
|---|
| 13 | | -/// Maximum message age for free-tier users (15 minutes). |
|---|
| 14 | | -const Duration kFreeTierMessageTtl = Duration(minutes: 15); |
|---|
| 15 | +/// Maximum message age for free-tier users. |
|---|
| 16 | +const Duration kFreeTierMessageTtl = Duration(minutes: 30); |
|---|
| 15 | 17 | |
|---|
| 16 | 18 | /// Shared preference key for caching purchase status locally. |
|---|
| 17 | 19 | const String _kProCacheKey = 'pailot_is_pro'; |
|---|
| .. | .. |
|---|
| 51 | 53 | |
|---|
| 52 | 54 | /// Initialize the service. Call once at app startup. |
|---|
| 53 | 55 | Future<void> initialize() async { |
|---|
| 54 | | - // Restore cached value immediately so UI doesn't flicker. |
|---|
| 55 | | - // Default to true for dev/sideloaded builds (no StoreKit configured). |
|---|
| 56 | + // Hydrate from local cache first so UI doesn't flash "Free" before the |
|---|
| 57 | + // store reply lands. The cache is authoritative for offline / cold-start |
|---|
| 58 | + // rendering; restorePurchases() reconciles it with the platform stores. |
|---|
| 56 | 59 | final prefs = await SharedPreferences.getInstance(); |
|---|
| 57 | | - // Force pro for now — clear any stale false value from earlier testing |
|---|
| 58 | | - _isPro = true; |
|---|
| 59 | | - await prefs.setBool(_kProCacheKey, true); |
|---|
| 60 | + _isPro = prefs.getBool(_kProCacheKey) ?? false; |
|---|
| 60 | 61 | notifyListeners(); |
|---|
| 61 | 62 | |
|---|
| 62 | | - // Check if IAP is available — may not be on dev/sideloaded builds |
|---|
| 63 | + // If the platform IAP layer isn't available (sideloaded dev build, no |
|---|
| 64 | + // network, simulator without StoreKit config), keep the cached value as-is |
|---|
| 65 | + // and skip the store dance. In debug, default to Pro so dev testing isn't |
|---|
| 66 | + // blocked by the paywall — but never override in release builds. |
|---|
| 63 | 67 | final available = await InAppPurchase.instance.isAvailable(); |
|---|
| 64 | 68 | if (!available) { |
|---|
| 65 | | - debugPrint('[IAP] StoreKit not available — assuming pro (dev build)'); |
|---|
| 66 | | - _isPro = true; |
|---|
| 67 | | - notifyListeners(); |
|---|
| 69 | + if (kDebugMode && !_isPro) { |
|---|
| 70 | + debugPrint('[IAP] Store unavailable in debug build — granting Pro for dev testing'); |
|---|
| 71 | + _isPro = true; |
|---|
| 72 | + await prefs.setBool(_kProCacheKey, true); |
|---|
| 73 | + notifyListeners(); |
|---|
| 74 | + } else { |
|---|
| 75 | + debugPrint('[IAP] Store unavailable; using cached isPro=$_isPro'); |
|---|
| 76 | + } |
|---|
| 68 | 77 | return; |
|---|
| 69 | 78 | } |
|---|
| 70 | 79 | |
|---|