mirror of
https://github.com/ev-map/EVMap.git
synced 2026-04-23 15:47:08 -04:00
implement donation view
This commit is contained in:
@@ -298,4 +298,8 @@ class FiltersAdapter : DataBindingAdapter<FilterWithValue<FilterValue>>() {
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
class DonationAdapter() : DataBindingAdapter<DonationItem>() {
|
||||
override fun getItemViewType(position: Int): Int = R.layout.item_donation
|
||||
}
|
||||
@@ -29,9 +29,6 @@ class AboutFragment : PreferenceFragmentCompat() {
|
||||
setPreferencesFromResource(R.xml.about, rootKey)
|
||||
|
||||
findPreference<Preference>("version")?.summary = BuildConfig.VERSION_NAME
|
||||
|
||||
//TODO: disable donations until fully implemented
|
||||
findPreference<Preference>("donate")?.isVisible = false
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
|
||||
|
||||
@@ -9,10 +9,14 @@ import androidx.appcompat.widget.Toolbar
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.adapter.DonationAdapter
|
||||
import net.vonforst.evmap.databinding.FragmentDonateBinding
|
||||
import net.vonforst.evmap.viewmodel.DonateViewModel
|
||||
|
||||
@@ -40,5 +44,21 @@ class DonateFragment : Fragment() {
|
||||
navController,
|
||||
(requireActivity() as MapsActivity).appBarConfiguration
|
||||
)
|
||||
|
||||
binding.productsList.apply {
|
||||
adapter = DonationAdapter().apply {
|
||||
onClickListener = {
|
||||
vm.startPurchase(it, requireActivity())
|
||||
}
|
||||
}
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
}
|
||||
|
||||
vm.purchaseSuccessful.observe(viewLifecycleOwner, Observer {
|
||||
Snackbar.make(view, R.string.donation_successful, Snackbar.LENGTH_LONG).show()
|
||||
})
|
||||
vm.purchaseFailed.observe(viewLifecycleOwner, Observer {
|
||||
Snackbar.make(view, R.string.donation_failed, Snackbar.LENGTH_LONG).show()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package net.vonforst.evmap.viewmodel
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.android.billingclient.api.*
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
import net.vonforst.evmap.adapter.Equatable
|
||||
|
||||
class DonateViewModel(application: Application) : AndroidViewModel(application),
|
||||
PurchasesUpdatedListener {
|
||||
@@ -12,18 +15,96 @@ class DonateViewModel(application: Application) : AndroidViewModel(application),
|
||||
.enablePendingPurchases()
|
||||
.build()
|
||||
|
||||
val products: MutableLiveData<List<SkuDetails>> by lazy {
|
||||
MutableLiveData<List<SkuDetails>>().apply {
|
||||
value = null
|
||||
init {
|
||||
billingClient.startConnection(object : BillingClientStateListener {
|
||||
override fun onBillingServiceDisconnected() {
|
||||
}
|
||||
|
||||
val params = SkuDetailsParams.newBuilder().setType(BillingClient.SkuType.INAPP).build()
|
||||
billingClient.querySkuDetailsAsync(params) { result, details ->
|
||||
value = details
|
||||
override fun onBillingSetupFinished(p0: BillingResult?) {
|
||||
loadProducts()
|
||||
|
||||
// consume pending purchases
|
||||
val purchases = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
|
||||
purchases.purchasesList.forEach {
|
||||
if (!it.isAcknowledged) {
|
||||
consumePurchase(it.purchaseToken, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
private fun loadProducts() {
|
||||
val params = SkuDetailsParams.newBuilder()
|
||||
.setType(BillingClient.SkuType.INAPP)
|
||||
.setSkusList(
|
||||
listOf(
|
||||
"donate_1_eur", "donate_2_eur", "donate_5_eur", "donate_10_eur"
|
||||
) +
|
||||
if (BuildConfig.DEBUG) {
|
||||
listOf(
|
||||
"android.test.purchased", "android.test.canceled",
|
||||
"android.test.item_unavailable"
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
)
|
||||
.build()
|
||||
billingClient.querySkuDetailsAsync(params) { result, details ->
|
||||
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||
products.value = Resource.success(details.map { DonationItem(it) })
|
||||
} else {
|
||||
products.value = Resource.error(result.debugMessage, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPurchasesUpdated(result: BillingResult, purchases: List<Purchase>?) {
|
||||
TODO("Not yet implemented")
|
||||
val products: MutableLiveData<Resource<List<DonationItem>>> by lazy {
|
||||
MutableLiveData<Resource<List<DonationItem>>>().apply {
|
||||
value = Resource.loading(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val purchaseSuccessful = SingleLiveEvent<Nothing>()
|
||||
val purchaseFailed = SingleLiveEvent<Nothing>()
|
||||
|
||||
override fun onPurchasesUpdated(result: BillingResult, purchases: List<Purchase>?) {
|
||||
if (result.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
|
||||
for (purchase in purchases) {
|
||||
val purchaseToken = purchase.purchaseToken
|
||||
consumePurchase(purchaseToken)
|
||||
}
|
||||
} else if (result.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
|
||||
// Handle an error caused by a user cancelling the purchase flow.
|
||||
} else {
|
||||
purchaseFailed.call()
|
||||
}
|
||||
}
|
||||
|
||||
private fun consumePurchase(purchaseToken: String, showSuccess: Boolean = true) {
|
||||
val params = ConsumeParams.newBuilder()
|
||||
.setPurchaseToken(purchaseToken)
|
||||
.build()
|
||||
billingClient.consumeAsync(params) { _, _ ->
|
||||
if (showSuccess) purchaseSuccessful.call()
|
||||
}
|
||||
}
|
||||
|
||||
fun startPurchase(it: DonationItem, activity: Activity) {
|
||||
val flowParams = BillingFlowParams.newBuilder()
|
||||
.setSkuDetails(it.sku)
|
||||
.build()
|
||||
val response = billingClient.launchBillingFlow(activity, flowParams)
|
||||
if (response.responseCode != BillingClient.BillingResponseCode.OK) {
|
||||
purchaseFailed.call()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
billingClient.endConnection()
|
||||
}
|
||||
}
|
||||
|
||||
data class DonationItem(val sku: SkuDetails) : Equatable
|
||||
@@ -1,7 +1,10 @@
|
||||
package net.vonforst.evmap.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.Nullable
|
||||
import androidx.lifecycle.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
|
||||
inline fun <VM : ViewModel> viewModelFactory(crossinline f: () -> VM) =
|
||||
object : ViewModelProvider.Factory {
|
||||
@@ -32,4 +35,31 @@ data class Resource<out T>(val status: Status, val data: T?, val message: String
|
||||
return Resource(Status.LOADING, data, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||
private val mPending: AtomicBoolean = AtomicBoolean(false)
|
||||
|
||||
@MainThread
|
||||
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
|
||||
super.observe(owner, Observer {
|
||||
if (mPending.compareAndSet(true, false)) {
|
||||
observer.onChanged(it)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun setValue(@Nullable t: T?) {
|
||||
mPending.set(true)
|
||||
super.setValue(t)
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for cases where T is Void, to make calls cleaner.
|
||||
*/
|
||||
@MainThread
|
||||
fun call() {
|
||||
value = null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user