From 5da45c38a317570075feef38c92a2ea925ff5790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Sena?= Date: Tue, 27 Aug 2024 13:53:42 -0300 Subject: [PATCH] Remove the billing library --- app/build.gradle.kts | 1 - app/src/main/AndroidManifest.xml | 1 - .../uefs/core/billing/SkuResults.kt | 33 --- .../storage/repository/BillingRepository.kt | 74 ------ .../uefs/core/vm/BillingViewModel.kt | 241 ------------------ .../purchases/PurchaseBindingAdapter.kt | 53 ---- .../feature/purchases/PurchasesFragment.kt | 112 -------- .../feature/purchases/SkuDetailsAdapter.kt | 54 ---- app/src/main/res/layout/item_sku_details.xml | 95 ------- .../main/res/navigation/home_nav_graph.xml | 6 - 10 files changed, 670 deletions(-) delete mode 100644 app/src/main/java/com/forcetower/uefs/core/billing/SkuResults.kt delete mode 100644 app/src/main/java/com/forcetower/uefs/core/storage/repository/BillingRepository.kt delete mode 100644 app/src/main/java/com/forcetower/uefs/core/vm/BillingViewModel.kt delete mode 100644 app/src/main/java/com/forcetower/uefs/feature/purchases/PurchaseBindingAdapter.kt delete mode 100644 app/src/main/java/com/forcetower/uefs/feature/purchases/PurchasesFragment.kt delete mode 100644 app/src/main/java/com/forcetower/uefs/feature/purchases/SkuDetailsAdapter.kt delete mode 100644 app/src/main/res/layout/item_sku_details.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4d0f00afe..c765f8d0b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -232,7 +232,6 @@ dependencies { implementation(libs.play.services.games.v2) implementation(libs.play.services.auth) implementation(libs.play.services.location) - implementation(libs.billing) implementation(libs.review.ktx) implementation(libs.app.update.ktx) implementation(libs.feature.delivery.ktx) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a4a51c243..f451135ee 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,7 +33,6 @@ - - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.forcetower.uefs.core.billing - -import com.android.billingclient.api.ProductDetails - -data class SkuDetailsResult( - val responseCode: Int, - val list: List? = null -) - -data class SkuConsumeResult( - val responseCode: Int, - val token: String? = null -) diff --git a/app/src/main/java/com/forcetower/uefs/core/storage/repository/BillingRepository.kt b/app/src/main/java/com/forcetower/uefs/core/storage/repository/BillingRepository.kt deleted file mode 100644 index 480814405..000000000 --- a/app/src/main/java/com/forcetower/uefs/core/storage/repository/BillingRepository.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * This file is part of the UNES Open Source Project. - * UNES is licensed under the GNU GPLv3. - * - * Copyright (c) 2020. João Paulo Sena - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.forcetower.uefs.core.storage.repository - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.map -import com.android.billingclient.api.Purchase -import com.forcetower.uefs.core.effects.purchases.PurchaseEffect -import com.forcetower.uefs.core.effects.purchases.SubscriptionEffect -import com.forcetower.uefs.core.storage.database.UDatabase -import com.google.firebase.firestore.FirebaseFirestore -import javax.inject.Inject -import javax.inject.Named - -class BillingRepository @Inject constructor( - private val firestore: FirebaseFirestore, - private val database: UDatabase, - @Named("scoreIncreaseEffect") private val scoreIncreaseEffect: PurchaseEffect, - @Named("monkeyGoldEffect") private val monkeyGoldEffect: SubscriptionEffect -) { - - fun getUsername(): LiveData { - val source = database.accessDao().getAccess() - return source.map { it?.username } - } - - fun getManagedSkus(): LiveData> { - val result = MutableLiveData>() - firestore.collection("products_sku").addSnapshotListener { snapshot, _ -> - if (snapshot != null) { - val data = snapshot.documents.map { it.data?.get("sku") as String } - result.postValue(data) - } - } - return result - } - - fun handlePurchases(purchases: List) { - purchases.flatMap { it.products }.forEach { purchase -> - // TODO Make the effects consume the token if needed (should billing client be moved to dagger? Hum....) - when (purchase) { - "score_increase_common" -> scoreIncreaseEffect.runEffect() - "unes_gold_monkey" -> monkeyGoldEffect.runEffect() - } - } - } - - fun isGoldMonkey(): Boolean { - return monkeyGoldEffect.isEffectActive() - } - - fun cancelSubscriptions() { - monkeyGoldEffect.removeEffect() - } -} diff --git a/app/src/main/java/com/forcetower/uefs/core/vm/BillingViewModel.kt b/app/src/main/java/com/forcetower/uefs/core/vm/BillingViewModel.kt deleted file mode 100644 index 55229fe48..000000000 --- a/app/src/main/java/com/forcetower/uefs/core/vm/BillingViewModel.kt +++ /dev/null @@ -1,241 +0,0 @@ -/* - * This file is part of the UNES Open Source Project. - * UNES is licensed under the GNU GPLv3. - * - * Copyright (c) 2020. João Paulo Sena - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.forcetower.uefs.core.vm - -import android.app.Activity -import android.content.Context -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.switchMap -import androidx.lifecycle.viewModelScope -import com.android.billingclient.api.BillingClient -import com.android.billingclient.api.BillingClientStateListener -import com.android.billingclient.api.BillingFlowParams -import com.android.billingclient.api.BillingResult -import com.android.billingclient.api.ConsumeParams -import com.android.billingclient.api.ProductDetails -import com.android.billingclient.api.Purchase -import com.android.billingclient.api.PurchasesUpdatedListener -import com.android.billingclient.api.QueryProductDetailsParams -import com.android.billingclient.api.QueryPurchasesParams -import com.forcetower.core.lifecycle.Event -import com.forcetower.uefs.R -import com.forcetower.uefs.core.billing.SkuDetailsResult -import com.forcetower.uefs.core.storage.repository.BillingRepository -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import timber.log.Timber - -@HiltViewModel -class BillingViewModel @Inject constructor( - context: Context, - private val repository: BillingRepository -) : ViewModel(), PurchasesUpdatedListener, BillingClientStateListener { - private val _selectSku = MutableLiveData>() - val selectSku: LiveData> - get() = _selectSku - - private val _snack = MutableLiveData>() - val snack: LiveData> - get() = _snack - - private val billingClient = BillingClient.newBuilder(context.applicationContext) - .enablePendingPurchases() - .setListener(this) - .build() - - init { - if (!billingClient.isReady) { - billingClient.startConnection(this) - } - } - - private suspend fun queryGoldMonkey(): Boolean { - try { - val purchases = billingClient.suspendQueryPurchases(BillingClient.ProductType.SUBS) - if (purchases.isEmpty()) { - repository.cancelSubscriptions() - } else { - repository.handlePurchases(purchases) - } - } catch (error: Throwable) { - Timber.i(error, "Error during purchase update") - } - return repository.isGoldMonkey() - } - - val currentUsername: LiveData - get() = repository.getUsername() - - fun getSkus() = repository.getManagedSkus() - - val subscriptions = getSkus().switchMap { skuList -> getSubscriptions(skuList) } - val inAppProducts = getSkus().switchMap { skuList -> getInAppProducts(skuList) } - - fun selectSku(skuDetails: ProductDetails?) { - skuDetails ?: return - _selectSku.value = Event(skuDetails) - } - - private fun getSubscriptions(list: List): LiveData { - val subscriptions = MutableLiveData() - val products = list.map { - QueryProductDetailsParams.Product.newBuilder() - .setProductType(BillingClient.ProductType.SUBS) - .setProductId(it) - .build() - } - - val request = QueryProductDetailsParams.newBuilder() - .setProductList(products) - .build() - - billingClient.queryProductDetailsAsync(request) { response, details -> - subscriptions.value = SkuDetailsResult(response.responseCode, details) - } - return subscriptions - } - - private fun getInAppProducts(list: List): LiveData { - val inAppProducts = MutableLiveData() - val products = list.map { - QueryProductDetailsParams.Product.newBuilder() - .setProductType(BillingClient.ProductType.INAPP) - .setProductId(it) - .build() - } - - val request = QueryProductDetailsParams.newBuilder() - .setProductList(products) - .build() - - billingClient.queryProductDetailsAsync(request) { response, details -> - inAppProducts.value = SkuDetailsResult(response.responseCode, details) - } - return inAppProducts - } - - override fun onPurchasesUpdated(result: BillingResult, purchases: List?) { - when (result.responseCode) { - BillingClient.BillingResponseCode.OK -> { - if (purchases != null) { - handlePurchases(purchases) - } - } - BillingClient.BillingResponseCode.USER_CANCELED -> { - Timber.d("User canceled purchase") - } - BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> { - _snack.postValue(Event(R.string.purchase_item_already_owned)) - } - else -> { - _snack.postValue(Event(R.string.purchase_update_error)) - Timber.e("Error purchase: ${result.responseCode}") - } - } - } - - fun launchBillingFlow(activity: Activity, details: ProductDetails, username: String) { - val productParams = BillingFlowParams.ProductDetailsParams.newBuilder() - .setProductDetails(details) - .build() - - val params = BillingFlowParams.newBuilder() - .setProductDetailsParamsList(listOf(productParams)) - .setObfuscatedAccountId(username) - .build() - - billingClient.launchBillingFlow(activity, params) - } - - private suspend fun updatePurchases() { - val result = try { - billingClient.suspendQueryPurchases(BillingClient.ProductType.INAPP) - } catch (error: Throwable) { - null - } - - if (result == null) { - Timber.d("Update purchase: Null purchase list") - } else { - handlePurchases(result) - } - } - - private fun handlePurchases(purchases: List) { - repository.handlePurchases(purchases) - purchases.forEach { - if (!it.isAutoRenewing) { - consume(it) - } else { - _snack.postValue(Event(R.string.purchase_subscription_started)) - } - } - } - - override fun onBillingServiceDisconnected() = Unit - - override fun onBillingSetupFinished(result: BillingResult) { - if (result.responseCode == BillingClient.BillingResponseCode.OK) { - viewModelScope.launch { - updatePurchases() - } - } - } - - override fun onCleared() { - super.onCleared() - billingClient.endConnection() - } - - private fun consume(purchase: Purchase) { - val params = ConsumeParams.newBuilder().setPurchaseToken(purchase.purchaseToken).build() - billingClient.consumeAsync(params) { response, token -> - Timber.d("Attempt to consume $token finished with code ${response.responseCode}") - if (response.responseCode == BillingClient.BillingResponseCode.OK) { - _snack.postValue(Event(R.string.purchase_you_bought_a_consumable_item)) - } else { - Timber.e("Failed to consume ${purchase.products}") - } - } - } - - private suspend fun BillingClient.suspendQueryPurchases( - @BillingClient.ProductType type: String - ) = suspendCancellableCoroutine> { continuation -> - val params = QueryPurchasesParams.newBuilder() - .setProductType(type) - .build() - - queryPurchasesAsync(params) { result, purchases -> - if (result.responseCode == BillingClient.BillingResponseCode.OK) { - continuation.resume(purchases) - } else { - continuation.resumeWithException(IllegalStateException("Response code was ${result.responseCode}")) - } - } - } -} diff --git a/app/src/main/java/com/forcetower/uefs/feature/purchases/PurchaseBindingAdapter.kt b/app/src/main/java/com/forcetower/uefs/feature/purchases/PurchaseBindingAdapter.kt deleted file mode 100644 index b5883fc40..000000000 --- a/app/src/main/java/com/forcetower/uefs/feature/purchases/PurchaseBindingAdapter.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of the UNES Open Source Project. - * UNES is licensed under the GNU GPLv3. - * - * Copyright (c) 2020. João Paulo Sena - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.forcetower.uefs.feature.purchases - -import android.widget.TextView -import androidx.databinding.BindingAdapter -import com.android.billingclient.api.BillingClient -import com.android.billingclient.api.ProductDetails -import com.forcetower.uefs.R - -@BindingAdapter(value = ["skuPrice"]) -fun price(tv: TextView, price: String?) { - val value = price ?: "???,??" - tv.text = tv.context.getString(R.string.sku_price_format, "R$", value) -} - -@BindingAdapter(value = ["skuTitle"]) -fun title(tv: TextView, title: String?) { - val value = title ?: "Nem sei" - if (value.contains("(")) { - val index = value.lastIndexOf("(") - val corrected = value.substring(0, index).trim() - tv.text = corrected - } else { - tv.text = value - } -} - -@BindingAdapter("iapPrice") -fun TextView.iapPrice(details: ProductDetails?) { - details ?: return - if (details.productType != BillingClient.ProductType.INAPP) return - val price = details.oneTimePurchaseOfferDetails?.formattedPrice ?: "???,??" - text = price -} diff --git a/app/src/main/java/com/forcetower/uefs/feature/purchases/PurchasesFragment.kt b/app/src/main/java/com/forcetower/uefs/feature/purchases/PurchasesFragment.kt deleted file mode 100644 index 3fc7a20c9..000000000 --- a/app/src/main/java/com/forcetower/uefs/feature/purchases/PurchasesFragment.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * This file is part of the UNES Open Source Project. - * UNES is licensed under the GNU GPLv3. - * - * Copyright (c) 2020. João Paulo Sena - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.forcetower.uefs.feature.purchases - -import android.content.SharedPreferences -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.activityViewModels -import com.android.billingclient.api.BillingClient -import com.android.billingclient.api.ProductDetails -import com.forcetower.core.lifecycle.EventObserver -import com.forcetower.uefs.R -import com.forcetower.uefs.core.billing.SkuDetailsResult -import com.forcetower.uefs.core.vm.BillingViewModel -import com.forcetower.uefs.databinding.FragmentPurchasesBinding -import com.forcetower.uefs.feature.shared.UFragment -import com.google.android.material.snackbar.Snackbar -import com.google.firebase.analytics.FirebaseAnalytics -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@AndroidEntryPoint -class PurchasesFragment : UFragment() { - @Inject lateinit var preferences: SharedPreferences - - @Inject lateinit var analytics: FirebaseAnalytics - - private val viewModel: BillingViewModel by activityViewModels() - private lateinit var binding: FragmentPurchasesBinding - private lateinit var skuAdapter: SkuDetailsAdapter - - private val details: MutableList = mutableListOf() - - private var currentUsername: String? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - if (savedInstanceState == null) { - analytics.logEvent("purchases_screen", null) - } - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return FragmentPurchasesBinding.inflate(inflater, container, false).also { - binding = it - }.apply { - imageTop = "https://cdn.dribbble.com/users/1903950/screenshots/4225909/02_main_tr__1.gif" - executePendingBindings() - incToolbar.textToolbarTitle.text = getString(R.string.label_purchases) - }.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - skuAdapter = SkuDetailsAdapter(viewModel) - binding.recyclerSku.apply { - adapter = skuAdapter - } - viewModel.subscriptions.observe(viewLifecycleOwner) { - processDetails(it) - } - viewModel.selectSku.observe( - viewLifecycleOwner, - EventObserver { - purchaseFlow(it) - } - ) - viewModel.currentUsername.observe(viewLifecycleOwner) { - currentUsername = it - } - } - - private fun processDetails(result: SkuDetailsResult) { - if (result.responseCode == BillingClient.BillingResponseCode.OK) { - val values = result.list - details.clear() - if (values != null) details.addAll(values) - skuAdapter.submitList(values) - } else { - showSnack("${getString(R.string.donation_service_response_error)} ${result.responseCode}", Snackbar.LENGTH_LONG) - analytics.logEvent("purchases_failed", null) - } - } - - private fun purchaseFlow(details: ProductDetails) { - val username = currentUsername - if (username != null) { - viewModel.launchBillingFlow(requireActivity(), details, username) - } - } -} diff --git a/app/src/main/java/com/forcetower/uefs/feature/purchases/SkuDetailsAdapter.kt b/app/src/main/java/com/forcetower/uefs/feature/purchases/SkuDetailsAdapter.kt deleted file mode 100644 index 6eec5a5eb..000000000 --- a/app/src/main/java/com/forcetower/uefs/feature/purchases/SkuDetailsAdapter.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This file is part of the UNES Open Source Project. - * UNES is licensed under the GNU GPLv3. - * - * Copyright (c) 2020. João Paulo Sena - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.forcetower.uefs.feature.purchases - -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import com.android.billingclient.api.ProductDetails -import com.forcetower.uefs.R -import com.forcetower.uefs.core.vm.BillingViewModel -import com.forcetower.uefs.databinding.ItemSkuDetailsBinding -import com.forcetower.uefs.feature.shared.inflate - -class SkuDetailsAdapter( - private val viewModel: BillingViewModel -) : ListAdapter(SkuDiff) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SkuHolder { - return SkuHolder(parent.inflate(R.layout.item_sku_details)) - } - - override fun onBindViewHolder(holder: SkuHolder, position: Int) { - holder.binding.apply { - skuDetails = getItem(position) - viewModel = this@SkuDetailsAdapter.viewModel - executePendingBindings() - } - } - - inner class SkuHolder(val binding: ItemSkuDetailsBinding) : RecyclerView.ViewHolder(binding.root) - - object SkuDiff : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: ProductDetails, newItem: ProductDetails) = oldItem.productId == newItem.productId - override fun areContentsTheSame(oldItem: ProductDetails, newItem: ProductDetails) = oldItem == newItem - } -} diff --git a/app/src/main/res/layout/item_sku_details.xml b/app/src/main/res/layout/item_sku_details.xml deleted file mode 100644 index 47e2c1a51..000000000 --- a/app/src/main/res/layout/item_sku_details.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/home_nav_graph.xml b/app/src/main/res/navigation/home_nav_graph.xml index 0d4ef8496..e2b9a586c 100644 --- a/app/src/main/res/navigation/home_nav_graph.xml +++ b/app/src/main/res/navigation/home_nav_graph.xml @@ -106,12 +106,6 @@ android:label="RemindersFragment" tools:layout="@layout/fragment_reminders"/> - -