Add notification preferences UI and subscription verification on launch
- Add NotificationPreferencesScreen (Android) and NotificationPreferencesView (iOS) - Add NotificationPreferencesViewModel for shared business logic - Wire up notification preferences from ProfileScreen on both platforms - Add subscription verification on app launch for iOS (StoreKit) and Android (Google Play Billing) - Update SubscriptionApi to match Go backend endpoints (/subscription/purchase/) - Update StoreKit Configuration with correct product IDs and pricing ($2.99/month, $27.99/year) - Update Android placeholder prices to match App Store pricing - Fix NotificationPreference model to match Go backend schema 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -30,10 +30,12 @@ import com.example.casera.storage.ThemeStorage
|
||||
import com.example.casera.storage.ThemeStorageManager
|
||||
import com.example.casera.ui.theme.ThemeManager
|
||||
import com.example.casera.fcm.FCMManager
|
||||
import com.example.casera.platform.BillingManager
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MainActivity : ComponentActivity(), SingletonImageLoader.Factory {
|
||||
private var deepLinkResetToken by mutableStateOf<String?>(null)
|
||||
private lateinit var billingManager: BillingManager
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
@@ -49,12 +51,18 @@ class MainActivity : ComponentActivity(), SingletonImageLoader.Factory {
|
||||
ThemeStorage.initialize(ThemeStorageManager.getInstance(applicationContext))
|
||||
ThemeManager.initialize()
|
||||
|
||||
// Initialize BillingManager for subscription management
|
||||
billingManager = BillingManager.getInstance(applicationContext)
|
||||
|
||||
// Handle deep link from intent
|
||||
handleDeepLink(intent)
|
||||
|
||||
// Request notification permission and setup FCM
|
||||
setupFCM()
|
||||
|
||||
// Verify subscriptions if user is authenticated
|
||||
verifySubscriptionsOnLaunch()
|
||||
|
||||
setContent {
|
||||
App(
|
||||
deepLinkResetToken = deepLinkResetToken,
|
||||
@@ -65,6 +73,36 @@ class MainActivity : ComponentActivity(), SingletonImageLoader.Factory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify subscriptions with Google Play and sync with backend on app launch
|
||||
*/
|
||||
private fun verifySubscriptionsOnLaunch() {
|
||||
val authToken = TokenStorage.getToken()
|
||||
if (authToken == null) {
|
||||
Log.d("MainActivity", "No auth token, skipping subscription verification")
|
||||
return
|
||||
}
|
||||
|
||||
Log.d("MainActivity", "🔄 Verifying subscriptions on launch...")
|
||||
|
||||
billingManager.startConnection(
|
||||
onSuccess = {
|
||||
Log.d("MainActivity", "✅ Billing connected, restoring purchases...")
|
||||
lifecycleScope.launch {
|
||||
val restored = billingManager.restorePurchases()
|
||||
if (restored) {
|
||||
Log.d("MainActivity", "✅ Subscriptions verified and synced with backend")
|
||||
} else {
|
||||
Log.d("MainActivity", "📦 No active subscriptions found")
|
||||
}
|
||||
}
|
||||
},
|
||||
onError = { error ->
|
||||
Log.e("MainActivity", "❌ Failed to connect to billing: $error")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupFCM() {
|
||||
// Request notification permission if needed
|
||||
if (!FCMManager.isNotificationPermissionGranted(this)) {
|
||||
@@ -86,9 +124,15 @@ class MainActivity : ComponentActivity(), SingletonImageLoader.Factory {
|
||||
val authToken = TokenStorage.getToken()
|
||||
if (authToken != null) {
|
||||
val notificationApi = com.example.casera.network.NotificationApi()
|
||||
val deviceId = android.provider.Settings.Secure.getString(
|
||||
contentResolver,
|
||||
android.provider.Settings.Secure.ANDROID_ID
|
||||
)
|
||||
val request = com.example.casera.models.DeviceRegistrationRequest(
|
||||
deviceId = deviceId,
|
||||
registrationId = fcmToken,
|
||||
platform = "android"
|
||||
platform = "android",
|
||||
name = android.os.Build.MODEL
|
||||
)
|
||||
|
||||
when (val result = notificationApi.registerDevice(authToken, request)) {
|
||||
|
||||
@@ -33,9 +33,15 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
|
||||
val authToken = com.example.casera.storage.TokenStorage.getToken()
|
||||
if (authToken != null) {
|
||||
val notificationApi = com.example.casera.network.NotificationApi()
|
||||
val deviceId = android.provider.Settings.Secure.getString(
|
||||
applicationContext.contentResolver,
|
||||
android.provider.Settings.Secure.ANDROID_ID
|
||||
)
|
||||
val request = com.example.casera.models.DeviceRegistrationRequest(
|
||||
deviceId = deviceId,
|
||||
registrationId = token,
|
||||
platform = "android"
|
||||
platform = "android",
|
||||
name = android.os.Build.MODEL
|
||||
)
|
||||
|
||||
when (val result = notificationApi.registerDevice(authToken, request)) {
|
||||
|
||||
@@ -350,6 +350,8 @@ class BillingManager private constructor(private val context: Context) {
|
||||
true
|
||||
} else {
|
||||
Log.d(TAG, "No active purchases to restore")
|
||||
// Still fetch subscription status from backend to get free tier limits
|
||||
APILayer.getSubscriptionStatus(forceRefresh = true)
|
||||
false
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user