diff --git a/app/build.gradle b/app/build.gradle index ad49b93..346fcd6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.kharagedition.tibetankeyboard" minSdkVersion 23 targetSdkVersion 36 - versionCode 42 - versionName "2.1.12" + versionCode 45 + versionName "2.1.15" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true diff --git a/app/src/main/java/com/kharagedition/tibetankeyboard/LoginActivity.kt b/app/src/main/java/com/kharagedition/tibetankeyboard/LoginActivity.kt index 7720277..176c024 100644 --- a/app/src/main/java/com/kharagedition/tibetankeyboard/LoginActivity.kt +++ b/app/src/main/java/com/kharagedition/tibetankeyboard/LoginActivity.kt @@ -35,6 +35,9 @@ import com.kharagedition.tibetankeyboard.R import com.kharagedition.tibetankeyboard.UserPreferences import com.kharagedition.tibetankeyboard.repo.UserRepository import com.kharagedition.tibetankeyboard.ui.ChatActivity +import com.kharagedition.tibetankeyboard.subscription.RevenueCatManager +import com.kharagedition.tibetankeyboard.auth.AuthManager +import android.util.Log import kotlinx.coroutines.launch class LoginActivity : AppCompatActivity() { @@ -46,6 +49,7 @@ class LoginActivity : AppCompatActivity() { private lateinit var userPreferences: UserPreferences private lateinit var textViewPrivacyPolicy: TextView private lateinit var userRepository: UserRepository + private lateinit var revenueCatManager: RevenueCatManager private val googleSignInLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult() @@ -68,10 +72,12 @@ class LoginActivity : AppCompatActivity() { auth = Firebase.auth userRepository = UserRepository() userPreferences = UserPreferences(this) + revenueCatManager = RevenueCatManager.getInstance() // Check if user is already signed in if (auth.currentUser != null && userPreferences.isUserLoggedIn()) { - navigateToChatActivity() + Log.d("LoginActivity", "User already logged in, initializing RevenueCat...") + initializeRevenueCatAndNavigate() return } @@ -237,7 +243,25 @@ class LoginActivity : AppCompatActivity() { ) ) - navigateToChatActivity() + // CRITICAL: Initialize RevenueCat immediately after successful login + // This ensures purchases can be acknowledged with Google Play + Log.d("LoginActivity", "Initializing RevenueCat after successful login...") + revenueCatManager.initialize(this@LoginActivity, auth, object : RevenueCatManager.SubscriptionCallback { + override fun onSuccess(message: String) { + Log.d("LoginActivity", "✅ RevenueCat initialized successfully: $message") + navigateToChatActivity() + } + + override fun onError(error: String) { + Log.e("LoginActivity", "❌ RevenueCat initialization failed: $error") + // Still navigate even if RevenueCat fails + navigateToChatActivity() + } + + override fun onUserCancelled() { + // Not applicable here + } + }) } else { // Even if Firestore fails, continue with login but show warning Toast.makeText( @@ -245,7 +269,7 @@ class LoginActivity : AppCompatActivity() { "Welcome ${firebaseUser.displayName}! (Profile sync pending)", Toast.LENGTH_SHORT ).show() - navigateToChatActivity() + initializeRevenueCatAndNavigate() } } catch (e: Exception) { hideLoading() @@ -255,10 +279,30 @@ class LoginActivity : AppCompatActivity() { "Welcome ${firebaseUser.displayName}!", Toast.LENGTH_SHORT ).show() - navigateToChatActivity() + initializeRevenueCatAndNavigate() } } } + + /** + * Initialize RevenueCat after login and navigate to next screen + */ + private fun initializeRevenueCatAndNavigate() { + Log.d("LoginActivity", "Initializing RevenueCat after login...") + revenueCatManager.initialize(this, auth, object : RevenueCatManager.SubscriptionCallback { + override fun onSuccess(message: String) { + Log.d("LoginActivity", "✅ RevenueCat initialized: $message") + navigateToChatActivity() + } + + override fun onError(error: String) { + Log.e("LoginActivity", "❌ RevenueCat init failed: $error") + navigateToChatActivity() + } + + override fun onUserCancelled() {} + }) + } private fun handleLoginError(exception: Exception?) { val errorMessage = when (exception) { is FirebaseAuthUserCollisionException -> "An account already exists with this email" diff --git a/app/src/main/java/com/kharagedition/tibetankeyboard/TibetanKeyboard.kt b/app/src/main/java/com/kharagedition/tibetankeyboard/TibetanKeyboard.kt index b93e3bb..0412d6e 100644 --- a/app/src/main/java/com/kharagedition/tibetankeyboard/TibetanKeyboard.kt +++ b/app/src/main/java/com/kharagedition/tibetankeyboard/TibetanKeyboard.kt @@ -101,15 +101,29 @@ class TibetanKeyboard : InputMethodService(), OnKeyboardActionListener, AIKeyboa private fun setKeyBoardView() { val color = prefs.getString("colors", "#FF704C04") + val keyboardStyle = prefs.getString("keyboard_style", "classic") + keyboardView = when (color) { "#FF704C04" -> { - layoutInflater.inflate(R.layout.keyboard_brown, null) as TibetanKeyboardView + if (keyboardStyle == "modern") { + layoutInflater.inflate(R.layout.keyboard_brown_modern, null) as TibetanKeyboardView + } else { + layoutInflater.inflate(R.layout.keyboard_brown, null) as TibetanKeyboardView + } } "#FF000000" -> { - layoutInflater.inflate(R.layout.keyboard_black, null) as TibetanKeyboardView + if (keyboardStyle == "modern") { + layoutInflater.inflate(R.layout.keyboard_black_modern, null) as TibetanKeyboardView + } else { + layoutInflater.inflate(R.layout.keyboard_black, null) as TibetanKeyboardView + } } else -> { - layoutInflater.inflate(R.layout.keyboard_green, null) as TibetanKeyboardView + if (keyboardStyle == "modern") { + layoutInflater.inflate(R.layout.keyboard_green_modern, null) as TibetanKeyboardView + } else { + layoutInflater.inflate(R.layout.keyboard_green, null) as TibetanKeyboardView + } } } keyboardView?.setBackgroundColor(Color.parseColor(color)) diff --git a/app/src/main/java/com/kharagedition/tibetankeyboard/TibetanKeyboardApp.kt b/app/src/main/java/com/kharagedition/tibetankeyboard/TibetanKeyboardApp.kt index 9a6436f..19b45a0 100644 --- a/app/src/main/java/com/kharagedition/tibetankeyboard/TibetanKeyboardApp.kt +++ b/app/src/main/java/com/kharagedition/tibetankeyboard/TibetanKeyboardApp.kt @@ -31,21 +31,9 @@ class TibetanKeyboardApp : Application() { } private fun setUpRevenueCat() { + // DO NOT initialize RevenueCat here without a user ID + // RevenueCat will be configured in RevenueCatManager when user is authenticated + // This ensures purchases are always tied to the Firebase user ID Purchases.logLevel = LogLevel.DEBUG - val apiKey = if (BuildConfig.DEBUG) { - "goog_HqifnUJxdgpKcyrUFhRfJfAYIap" - } else { - "goog_HqifnUJxdgpKcyrUFhRfJfAYIap" - } - Purchases.configure( - PurchasesConfiguration.Builder(this, apiKey) - //.appUserID(null) - .purchasesAreCompletedBy(REVENUECAT) - .build() - - ) - Purchases.sharedInstance.updatedCustomerInfoListener = - UpdatedCustomerInfoListener { customerInfo -> Log.d("TAG", "onCreate: Customer Info Updated: ${customerInfo.toString()}") } - } } \ No newline at end of file diff --git a/app/src/main/java/com/kharagedition/tibetankeyboard/auth/AuthManager.kt b/app/src/main/java/com/kharagedition/tibetankeyboard/auth/AuthManager.kt index fd19edc..7344b6c 100644 --- a/app/src/main/java/com/kharagedition/tibetankeyboard/auth/AuthManager.kt +++ b/app/src/main/java/com/kharagedition/tibetankeyboard/auth/AuthManager.kt @@ -92,8 +92,8 @@ class AuthManager(private val context: Context) { return } - // Initialize RevenueCat with current user - revenueCatManager.initialize(auth, callback) + // Initialize RevenueCat with current user and Firebase UID + revenueCatManager.initialize(context, auth, callback) } /** diff --git a/app/src/main/java/com/kharagedition/tibetankeyboard/subscription/RevenueCatManager.kt b/app/src/main/java/com/kharagedition/tibetankeyboard/subscription/RevenueCatManager.kt index 46229f2..4d6de5c 100644 --- a/app/src/main/java/com/kharagedition/tibetankeyboard/subscription/RevenueCatManager.kt +++ b/app/src/main/java/com/kharagedition/tibetankeyboard/subscription/RevenueCatManager.kt @@ -2,6 +2,7 @@ package com.kharagedition.tibetankeyboard.subscription import android.app.Activity import android.content.Context +import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.google.firebase.auth.FirebaseAuth @@ -10,6 +11,7 @@ import com.kharagedition.tibetankeyboard.BuildConfig import com.revenuecat.purchases.* import com.revenuecat.purchases.interfaces.* import com.revenuecat.purchases.models.StoreTransaction +import com.revenuecat.purchases.interfaces.SyncPurchasesCallback /** * Singleton class to manage RevenueCat subscription logic @@ -21,6 +23,7 @@ class RevenueCatManager private constructor() { @Volatile private var INSTANCE: RevenueCatManager? = null private const val PREMIUM_ENTITLEMENT_ID = "pro" + private const val TAG = "RevenueCatManager" fun getInstance(): RevenueCatManager { return INSTANCE ?: synchronized(this) { @@ -29,6 +32,8 @@ class RevenueCatManager private constructor() { } } + private var isConfigured = false + private val _isPremiumUser = MutableLiveData() val isPremiumUser: LiveData = _isPremiumUser @@ -48,34 +53,117 @@ class RevenueCatManager private constructor() { } /** - * Initialize RevenueCat with Firebase user + * Initialize RevenueCat SDK with Firebase user ID + * This must be called BEFORE any purchase operations */ - fun initialize(firebaseAuth: FirebaseAuth, callback: SubscriptionCallback? = null) { + fun initialize(context: Context, firebaseAuth: FirebaseAuth, callback: SubscriptionCallback? = null) { val currentUser = firebaseAuth.currentUser if (currentUser == null) { + Log.e(TAG, "Cannot initialize RevenueCat: User not authenticated") callback?.onError("User not authenticated") return } + val userId = currentUser.uid + Log.d(TAG, "Initializing RevenueCat with Firebase UID: $userId") + _isLoading.value = true - Purchases.sharedInstance.logIn( - currentUser.uid, - object : LogInCallback { - override fun onReceived(customerInfo: CustomerInfo, created: Boolean) { - println("RevenueCat: User logged in successfully. Created: $created") - updatePremiumStatus(customerInfo) - fetchOfferings(callback) + // Configure RevenueCat SDK with the Firebase user ID + if (!isConfigured) { + try { + val apiKey = if (BuildConfig.DEBUG) { + "goog_HqifnUJxdgpKcyrUFhRfJfAYIap" + } else { + "goog_HqifnUJxdgpKcyrUFhRfJfAYIap" } - override fun onError(error: PurchasesError) { - _isLoading.value = false - val errorMsg = "Failed to initialize premium services: ${error.message}" - _error.value = errorMsg - callback?.onError(errorMsg) - } + Purchases.configure( + PurchasesConfiguration.Builder(context, apiKey) + .appUserID(userId) // CRITICAL: Set Firebase UID as app user ID + .purchasesAreCompletedBy(PurchasesAreCompletedBy.REVENUECAT) + .build() + ) + + // Setup customer info update listener + Purchases.sharedInstance.updatedCustomerInfoListener = + UpdatedCustomerInfoListener { customerInfo -> + Log.d(TAG, "Customer Info Updated: ${customerInfo.originalAppUserId}") + updatePremiumStatus(customerInfo) + } + + isConfigured = true + Log.d(TAG, "RevenueCat SDK configured successfully with user: $userId") + + // CRITICAL: Sync any pending purchases to acknowledge them with Google Play + syncPurchasesWithGooglePlay() + + // Fetch customer info and offerings + fetchCustomerInfoAndOfferings(callback) + + } catch (e: Exception) { + _isLoading.value = false + val errorMsg = "Failed to configure RevenueCat: ${e.message}" + Log.e(TAG, errorMsg, e) + _error.value = errorMsg + callback?.onError(errorMsg) } - ) + } else { + // Already configured, just refresh customer info + Log.d(TAG, "RevenueCat already configured, refreshing customer info") + + // CRITICAL: Sync purchases to ensure Google Play acknowledgment + syncPurchasesWithGooglePlay() + + fetchCustomerInfoAndOfferings(callback) + } + } + + /** + * Sync purchases with Google Play to acknowledge them + * This is CRITICAL to prevent Google from auto-cancelling subscriptions after 3 days + */ + private fun syncPurchasesWithGooglePlay() { + if (!isConfigured) { + Log.w(TAG, "Cannot sync purchases: RevenueCat not configured") + return + } + + Log.d(TAG, "Syncing purchases with Google Play to acknowledge subscriptions...") + Purchases.sharedInstance.syncPurchases(object : SyncPurchasesCallback { + override fun onSuccess(customerInfo: CustomerInfo) { + Log.d(TAG, "✅ Purchases synced successfully with Google Play") + Log.d(TAG, "Active subscriptions after sync: ${customerInfo.activeSubscriptions.joinToString()}") + updatePremiumStatus(customerInfo) + } + + override fun onError(error: PurchasesError) { + Log.e(TAG, "❌ Failed to sync purchases: ${error.message}") + Log.e(TAG, "Error code: ${error.code}") + } + }) + } + + /** + * Fetch customer info and offerings after initialization + */ + private fun fetchCustomerInfoAndOfferings(callback: SubscriptionCallback?) { + Purchases.sharedInstance.getCustomerInfo(object : ReceiveCustomerInfoCallback { + override fun onReceived(customerInfo: CustomerInfo) { + Log.d(TAG, "Customer info received - App User ID: ${customerInfo.originalAppUserId}") + Log.d(TAG, "Active entitlements: ${customerInfo.activeSubscriptions}") + updatePremiumStatus(customerInfo) + fetchOfferings(callback) + } + + override fun onError(error: PurchasesError) { + _isLoading.value = false + val errorMsg = "Failed to fetch customer info: ${error.message}" + Log.e(TAG, errorMsg) + _error.value = errorMsg + callback?.onError(errorMsg) + } + }) } /** @@ -118,14 +206,25 @@ class RevenueCatManager private constructor() { /** * Check current customer info and update premium status + * Note: RevenueCat must be initialized first */ fun refreshCustomerInfo(callback: SubscriptionCallback? = null) { + if (!isConfigured) { + Log.w(TAG, "Cannot refresh customer info: RevenueCat not initialized") + _isPremiumUser.value = false + return + } + _isLoading.value = true + // CRITICAL: First sync purchases to acknowledge any pending subscriptions + syncPurchasesWithGooglePlay() + Purchases.sharedInstance.getCustomerInfo( object : ReceiveCustomerInfoCallback { override fun onReceived(customerInfo: CustomerInfo) { _isLoading.value = false + Log.d(TAG, "Customer info refreshed - User ID: ${customerInfo.originalAppUserId}") updatePremiumStatus(customerInfo) callback?.onSuccess("Premium status updated") } @@ -133,6 +232,7 @@ class RevenueCatManager private constructor() { override fun onError(error: PurchasesError) { _isLoading.value = false val errorMsg = "Failed to load premium status: ${error.message}" + Log.e(TAG, errorMsg) _error.value = errorMsg callback?.onError(errorMsg) } @@ -140,16 +240,57 @@ class RevenueCatManager private constructor() { ) } + /** + * Force sync purchases with Google Play + * Call this when app resumes or after a purchase to ensure acknowledgment + */ + fun syncPurchases(callback: SubscriptionCallback? = null) { + if (!isConfigured) { + Log.w(TAG, "Cannot sync purchases: RevenueCat not initialized") + callback?.onError("RevenueCat not initialized") + return + } + + Log.d(TAG, "🔄 Manually syncing purchases with Google Play...") + Purchases.sharedInstance.syncPurchases(object : SyncPurchasesCallback { + override fun onSuccess(customerInfo: CustomerInfo) { + Log.d(TAG, "✅ Manual sync successful!") + updatePremiumStatus(customerInfo) + callback?.onSuccess("Purchases synced successfully") + } + + override fun onError(error: PurchasesError) { + Log.e(TAG, "❌ Manual sync failed: ${error.message}") + callback?.onError("Sync failed: ${error.message}") + } + }) + } + /** * Purchase premium subscription */ fun purchasePremium(activity: Activity, callback: SubscriptionCallback) { + if (!isConfigured) { + val errorMsg = "RevenueCat not initialized. Please login first." + Log.e(TAG, errorMsg) + callback.onError(errorMsg) + return + } + val packageToPurchase = premiumPackage if (packageToPurchase == null) { + Log.e(TAG, "Premium package not available") callback.onError("Premium subscription not available") return } + val userId = FirebaseAuth.getInstance().currentUser?.uid + Log.d(TAG, "==== Starting Purchase ====") + Log.d(TAG, "Firebase User ID: $userId") + Log.d(TAG, "Package: ${packageToPurchase.identifier}") + Log.d(TAG, "Product: ${packageToPurchase.product.id}") + Log.d(TAG, "========================") + _isLoading.value = true val purchaseParams = PurchaseParams.Builder(activity, packageToPurchase).build() @@ -158,18 +299,35 @@ class RevenueCatManager private constructor() { object : PurchaseCallback { override fun onCompleted(storeTransaction: StoreTransaction, customerInfo: CustomerInfo) { _isLoading.value = false + Log.d(TAG, "==== Purchase Successful ====") + Log.d(TAG, "Transaction ID: ${storeTransaction.orderId}") + Log.d(TAG, "Product IDs: ${storeTransaction.productIds.joinToString()}") + Log.d(TAG, "Customer ID: ${customerInfo.originalAppUserId}") + Log.d(TAG, "===========================") + + // CRITICAL: Immediately sync purchases to acknowledge with Google Play + // This prevents the 3-day auto-cancellation issue + Log.d(TAG, "Syncing purchase immediately to acknowledge with Google Play...") + syncPurchasesWithGooglePlay() + updatePremiumStatus(customerInfo) callback.onSuccess("Premium subscription activated!") } override fun onError(error: PurchasesError, userCancelled: Boolean) { _isLoading.value = false + Log.e(TAG, "==== Purchase Error ====") + Log.e(TAG, "User Cancelled: $userCancelled") + Log.e(TAG, "Error Code: ${error.code}") + Log.e(TAG, "Error Message: ${error.message}") + Log.e(TAG, "=======================") when { userCancelled -> { callback.onUserCancelled() } error.code == PurchasesErrorCode.ProductAlreadyPurchasedError -> { + Log.d(TAG, "Product already purchased, refreshing customer info") callback.onError("You already own this subscription") // Refresh customer info to update UI refreshCustomerInfo() @@ -205,50 +363,76 @@ class RevenueCatManager private constructor() { * Update premium status based on customer info */ private fun updatePremiumStatus(customerInfo: CustomerInfo) { - val isPremium = customerInfo.entitlements[PREMIUM_ENTITLEMENT_ID]?.isActive == true - _isPremiumUser.value = BuildConfig.DEBUG || isPremium - - println("RevenueCat: Premium status - $isPremium") + val proEntitlement = customerInfo.entitlements[PREMIUM_ENTITLEMENT_ID] + val isPremium = proEntitlement?.isActive == true + + // CRITICAL FIX: Do not use debug mode bypass in production + _isPremiumUser.value = isPremium + + // Get subscription expiry date from entitlement + val expiryDate = proEntitlement?.expirationDate + val willRenew = proEntitlement?.willRenew ?: false + val periodType = proEntitlement?.periodType?.toString() ?: "unknown" + + // Enhanced logging for debugging + Log.d(TAG, "==== Premium Status Update ====") + Log.d(TAG, "App User ID: ${customerInfo.originalAppUserId}") + Log.d(TAG, "Premium Status: $isPremium") + Log.d(TAG, "Expiry Date: $expiryDate") + Log.d(TAG, "Will Renew: $willRenew") + Log.d(TAG, "Period Type: $periodType") + Log.d(TAG, "Active Subscriptions: ${customerInfo.activeSubscriptions.joinToString()}") + Log.d(TAG, "All Entitlements: ${customerInfo.entitlements.all.keys.joinToString()}") + Log.d(TAG, "Pro Entitlement Active: ${proEntitlement?.isActive}") + Log.d(TAG, "Request Date: ${customerInfo.requestDate}") + Log.d(TAG, "============================") val userId = FirebaseAuth.getInstance().currentUser?.uid val db = FirebaseFirestore.getInstance() - if(userId==null) return + if(userId==null) { + Log.w(TAG, "Cannot update Firestore: User ID is null") + return + } if (isPremium) { + // CRITICAL FIX: Use actual expiration date from entitlement, not request date val premiumDetails = hashMapOf( "isPremium" to true, "subscribed" to true, "isSubscribed" to true, "subscriptionType" to "premium", - "activeSubscription" to customerInfo.activeSubscriptions.toList(), - "premiumExpiryDate" to customerInfo.requestDate, - "activeSubscriptions" to customerInfo.activeSubscriptions.toList() + "activeSubscriptions" to customerInfo.activeSubscriptions.toList(), + "premiumExpiryDate" to (expiryDate ?: customerInfo.requestDate), // Use actual expiry or fallback to request date + "willRenew" to willRenew, + "periodType" to periodType, + "revenueCatUserId" to customerInfo.originalAppUserId, + "lastUpdated" to customerInfo.requestDate, + "originalPurchaseDate" to proEntitlement.originalPurchaseDate ) val userRef = db.collection("users").document(userId) - userRef.update(premiumDetails) .addOnSuccessListener { - println("Firestore: User premium details updated successfully.") + Log.d(TAG, "Firestore: User premium details updated successfully for user: $userId") + Log.d(TAG, "Firestore: Subscription expires at: $expiryDate") } .addOnFailureListener { e -> - println("Firestore: Failed to update premium details - ${e.message}") + Log.e(TAG, "Firestore: Failed to update premium details - ${e.message}", e) } - - println("RevenueCat: Active subscriptions count - ${customerInfo.activeSubscriptions.size}") } else { val userRef = db.collection("users").document(userId) - userRef.update(mapOf( + userRef.update(mapOf( "isPremium" to false, "subscribed" to false, "isSubscribed" to false, + "lastUpdated" to customerInfo.requestDate )) .addOnSuccessListener { - println("Firestore: User premium status set to false.") + Log.d(TAG, "Firestore: User premium status set to false for user: $userId") } .addOnFailureListener { e -> - println("Firestore: Failed to set premium status - ${e.message}") + Log.e(TAG, "Firestore: Failed to set premium status - ${e.message}", e) } } } diff --git a/app/src/main/java/com/kharagedition/tibetankeyboard/ui/HomeActivity.kt b/app/src/main/java/com/kharagedition/tibetankeyboard/ui/HomeActivity.kt index 75a2fbe..0e9ab4f 100644 --- a/app/src/main/java/com/kharagedition/tibetankeyboard/ui/HomeActivity.kt +++ b/app/src/main/java/com/kharagedition/tibetankeyboard/ui/HomeActivity.kt @@ -42,6 +42,26 @@ class HomeActivity : InputMethodActivity() { override fun onResume() { checkKeyboardIsEnabledOrNot() + + // CRITICAL: Sync purchases when app resumes to acknowledge any pending subscriptions + // This prevents Google Play from auto-cancelling subscriptions after 3 days + if (authManager.isUserAuthenticated()) { + Log.d("HomeActivity", "onResume: Syncing purchases with Google Play...") + RevenueCatManager.getInstance().syncPurchases(object : RevenueCatManager.SubscriptionCallback { + override fun onSuccess(message: String) { + Log.d("HomeActivity", "✅ Purchases synced in onResume") + } + + override fun onError(error: String) { + Log.w("HomeActivity", "⚠️ Sync failed in onResume: $error (This is OK if RevenueCat is still initializing)") + } + + override fun onUserCancelled() {} + }) + } else { + Log.w("HomeActivity", "onResume: User not authenticated, skipping purchase sync") + } + premiumListener() super.onResume() } @@ -58,6 +78,28 @@ class HomeActivity : InputMethodActivity() { homeBinding = ActivityHomeBinding.inflate(layoutInflater) setContentView(homeBinding.root) authManager = AuthManager(this) + + // CRITICAL: Initialize user session and RevenueCat + // This is essential for purchase acknowledgment + if (authManager.isUserAuthenticated()) { + Log.d("HomeActivity", "User authenticated, initializing RevenueCat...") + authManager.initializeUserSession(object : RevenueCatManager.SubscriptionCallback { + override fun onSuccess(message: String) { + Log.d("HomeActivity", "✅ RevenueCat initialized in HomeActivity: $message") + } + + override fun onError(error: String) { + Log.e("HomeActivity", "❌ RevenueCat initialization failed: $error") + } + + override fun onUserCancelled() {} + }) + } else { + Log.w("HomeActivity", "User NOT authenticated - RevenueCat will not initialize") + // Optionally redirect to login if required + // authManager.redirectToLogin() + } + checkKeyboardIsEnabledOrNot() initClickListener() updateManager = UpdateNotificationManager(this) diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_24.xml new file mode 100644 index 0000000..704e217 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_keyboard_24.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/drawable/key_black_background_classic.xml b/app/src/main/res/drawable/key_black_background_classic.xml new file mode 100644 index 0000000..c0a84aa --- /dev/null +++ b/app/src/main/res/drawable/key_black_background_classic.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/key_black_background_modern.xml b/app/src/main/res/drawable/key_black_background_modern.xml new file mode 100644 index 0000000..bf1629b --- /dev/null +++ b/app/src/main/res/drawable/key_black_background_modern.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/key_brown_background_classic.xml b/app/src/main/res/drawable/key_brown_background_classic.xml new file mode 100644 index 0000000..5b3445a --- /dev/null +++ b/app/src/main/res/drawable/key_brown_background_classic.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/key_brown_background_modern.xml b/app/src/main/res/drawable/key_brown_background_modern.xml new file mode 100644 index 0000000..413bfa8 --- /dev/null +++ b/app/src/main/res/drawable/key_brown_background_modern.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/key_green_background_classic.xml b/app/src/main/res/drawable/key_green_background_classic.xml new file mode 100644 index 0000000..1d77fb4 --- /dev/null +++ b/app/src/main/res/drawable/key_green_background_classic.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/key_green_background_modern.xml b/app/src/main/res/drawable/key_green_background_modern.xml new file mode 100644 index 0000000..6b26b09 --- /dev/null +++ b/app/src/main/res/drawable/key_green_background_modern.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/normal.xml b/app/src/main/res/drawable/normal.xml index deef70c..af7fab5 100644 --- a/app/src/main/res/drawable/normal.xml +++ b/app/src/main/res/drawable/normal.xml @@ -1,10 +1,5 @@ - diff --git a/app/src/main/res/drawable/normal_classic.xml b/app/src/main/res/drawable/normal_classic.xml new file mode 100644 index 0000000..b53911c --- /dev/null +++ b/app/src/main/res/drawable/normal_classic.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/normal_classic_black.xml b/app/src/main/res/drawable/normal_classic_black.xml new file mode 100644 index 0000000..03a1a2d --- /dev/null +++ b/app/src/main/res/drawable/normal_classic_black.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/normal_classic_brown.xml b/app/src/main/res/drawable/normal_classic_brown.xml new file mode 100644 index 0000000..b53911c --- /dev/null +++ b/app/src/main/res/drawable/normal_classic_brown.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/normal_classic_green.xml b/app/src/main/res/drawable/normal_classic_green.xml new file mode 100644 index 0000000..790ccdf --- /dev/null +++ b/app/src/main/res/drawable/normal_classic_green.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/normal_modern.xml b/app/src/main/res/drawable/normal_modern.xml new file mode 100644 index 0000000..d7810aa --- /dev/null +++ b/app/src/main/res/drawable/normal_modern.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/normal_modern_black.xml b/app/src/main/res/drawable/normal_modern_black.xml new file mode 100644 index 0000000..bc8f8fb --- /dev/null +++ b/app/src/main/res/drawable/normal_modern_black.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/normal_modern_brown.xml b/app/src/main/res/drawable/normal_modern_brown.xml new file mode 100644 index 0000000..d7810aa --- /dev/null +++ b/app/src/main/res/drawable/normal_modern_brown.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/normal_modern_green.xml b/app/src/main/res/drawable/normal_modern_green.xml new file mode 100644 index 0000000..e5a98c5 --- /dev/null +++ b/app/src/main/res/drawable/normal_modern_green.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/pressed.xml b/app/src/main/res/drawable/pressed.xml index b294d3f..9efe335 100644 --- a/app/src/main/res/drawable/pressed.xml +++ b/app/src/main/res/drawable/pressed.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/pressed_classic.xml b/app/src/main/res/drawable/pressed_classic.xml new file mode 100644 index 0000000..4bd6760 --- /dev/null +++ b/app/src/main/res/drawable/pressed_classic.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/pressed_classic_black.xml b/app/src/main/res/drawable/pressed_classic_black.xml new file mode 100644 index 0000000..e6eaa4e --- /dev/null +++ b/app/src/main/res/drawable/pressed_classic_black.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/pressed_classic_brown.xml b/app/src/main/res/drawable/pressed_classic_brown.xml new file mode 100644 index 0000000..4bd6760 --- /dev/null +++ b/app/src/main/res/drawable/pressed_classic_brown.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/pressed_classic_green.xml b/app/src/main/res/drawable/pressed_classic_green.xml new file mode 100644 index 0000000..a590661 --- /dev/null +++ b/app/src/main/res/drawable/pressed_classic_green.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/pressed_modern.xml b/app/src/main/res/drawable/pressed_modern.xml new file mode 100644 index 0000000..f3af5ef --- /dev/null +++ b/app/src/main/res/drawable/pressed_modern.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/pressed_modern_black.xml b/app/src/main/res/drawable/pressed_modern_black.xml new file mode 100644 index 0000000..d9c9a70 --- /dev/null +++ b/app/src/main/res/drawable/pressed_modern_black.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/pressed_modern_brown.xml b/app/src/main/res/drawable/pressed_modern_brown.xml new file mode 100644 index 0000000..f3af5ef --- /dev/null +++ b/app/src/main/res/drawable/pressed_modern_brown.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/pressed_modern_green.xml b/app/src/main/res/drawable/pressed_modern_green.xml new file mode 100644 index 0000000..6a36257 --- /dev/null +++ b/app/src/main/res/drawable/pressed_modern_green.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/ai_keyboard_layout.xml b/app/src/main/res/layout/ai_keyboard_layout.xml index b80a0ee..0b82e7c 100644 --- a/app/src/main/res/layout/ai_keyboard_layout.xml +++ b/app/src/main/res/layout/ai_keyboard_layout.xml @@ -63,6 +63,7 @@ android:layout_marginHorizontal="4dp" android:elevation="2dp" />