Loading build.gradle +1 −1 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ buildscript { ext.exoplayer_version = "2.8.1" ext.glide_version = "4.8.0" ext.junit_version = '4.12' ext.kotlin_version = '1.3.31' ext.kotlin_version = '1.3.50' ext.lifecycle_version = '2.1.0' ext.material_version = '1.0.0' ext.mockito_version = '2.18.3' Loading presentation/build.gradle +1 −0 Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ android { signingConfigs.release.keyAlias = System.getenv("key_alias") signingConfigs.release.keyPassword = System.getenv("key_password") } } androidExtensions { Loading presentation/src/main/AndroidManifest.xml +2 −2 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- <?xml version="1.0" encoding="utf-8"?><!-- ~ Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com> ~ ~ This file is part of QKSMS. Loading Loading @@ -94,6 +93,7 @@ android:value=".common.util.QkChooserTargetService" /> </activity> <activity android:name=".feature.settings.SettingsActivity" /> <activity android:name=".feature.plus.PlusActivity" /> <activity android:name=".feature.gallery.GalleryActivity" android:theme="@style/AppTheme.Black" /> Loading presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt +12 −0 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import com.moez.QKSMS.feature.compose.ComposeActivity import com.moez.QKSMS.feature.conversationinfo.ConversationInfoActivity import com.moez.QKSMS.feature.gallery.GalleryActivity import com.moez.QKSMS.feature.notificationprefs.NotificationPrefsActivity import com.moez.QKSMS.feature.plus.PlusActivity import com.moez.QKSMS.feature.scheduled.ScheduledActivity import com.moez.QKSMS.feature.settings.SettingsActivity import com.moez.QKSMS.manager.AnalyticsManager Loading @@ -52,6 +53,7 @@ class Navigator @Inject constructor( private val context: Context, private val analyticsManager: AnalyticsManager, private val notificationManager: NotificationManager, private val billingManager: BillingManager, private val permissions: PermissionManager ) { Loading Loading @@ -149,6 +151,16 @@ class Navigator @Inject constructor( startActivityExternal(intent) } fun showFork() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms")) startActivity(intent) } fun showCopyright() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://gitlab.e.foundation/e/apps/message/blob/e-features/AUTHORS")) startActivity(intent) } fun showChangelog() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms/releases")) startActivityExternal(intent) Loading presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com> * * This file is part of QKSMS. * * QKSMS 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 * (at your option) any later version. * * QKSMS 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 QKSMS. If not, see <http://www.gnu.org/licenses/>. */ package com.moez.QKSMS.common.util import android.app.Activity import android.content.Context import com.android.billingclient.api.BillingClient import com.android.billingclient.api.BillingClient.BillingResponse import com.android.billingclient.api.BillingClient.SkuType import com.android.billingclient.api.BillingClientStateListener import com.android.billingclient.api.BillingFlowParams import com.android.billingclient.api.Purchase import com.android.billingclient.api.PurchasesUpdatedListener import com.android.billingclient.api.SkuDetails import com.android.billingclient.api.SkuDetailsParams import com.moez.QKSMS.BuildConfig import com.moez.QKSMS.manager.AnalyticsManager import io.reactivex.Flowable import io.reactivex.Observable import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @Singleton class BillingManager @Inject constructor( context: Context, private val analyticsManager: AnalyticsManager ) : PurchasesUpdatedListener { companion object { const val SKU_PLUS = "remove_ads" const val SKU_PLUS_DONATE = "qksms_plus_donate" } val products: Observable<List<SkuDetails>> = BehaviorSubject.create() val upgradeStatus: Observable<Boolean> private val skus = listOf(SKU_PLUS, SKU_PLUS_DONATE) private val purchaseListObservable = BehaviorSubject.create<List<Purchase>>() private val billingClient: BillingClient = BillingClient.newBuilder(context).setListener(this).build() private var isServiceConnected = false init { startServiceConnection { queryPurchases() querySkuDetailsAsync() } upgradeStatus = when (BuildConfig.FLAVOR) { "noAnalytics" -> BehaviorSubject.createDefault(true) else -> purchaseListObservable .map { purchases -> purchases.any { it.sku == SKU_PLUS } || purchases.any { it.sku == SKU_PLUS_DONATE } } .doOnNext { upgraded -> analyticsManager.setUserProperty("Upgraded", upgraded) } } } private fun queryPurchases() { executeServiceRequest { // Load the cached data purchaseListObservable.onNext(billingClient.queryPurchases(SkuType.INAPP).purchasesList.orEmpty()) // On a fresh device, the purchase might not be cached, and so we'll need to force a refresh billingClient.queryPurchaseHistoryAsync(SkuType.INAPP) { _, _ -> purchaseListObservable.onNext(billingClient.queryPurchases(SkuType.INAPP).purchasesList.orEmpty()) } } } private fun startServiceConnection(onSuccess: () -> Unit) { val listener = object : BillingClientStateListener { override fun onBillingSetupFinished(@BillingResponse billingResponseCode: Int) { if (billingResponseCode == BillingResponse.OK) { isServiceConnected = true onSuccess() } else { Timber.w("Billing response: $billingResponseCode") purchaseListObservable.onNext(listOf()) } } override fun onBillingServiceDisconnected() { isServiceConnected = false } } Flowable.fromCallable { billingClient.startConnection(listener) } .subscribeOn(Schedulers.io()) .subscribe() } private fun querySkuDetailsAsync() { executeServiceRequest { val subParams = SkuDetailsParams.newBuilder().setSkusList(skus).setType(BillingClient.SkuType.INAPP) billingClient.querySkuDetailsAsync(subParams.build()) { responseCode, skuDetailsList -> if (responseCode == BillingResponse.OK) { (products as Subject).onNext(skuDetailsList) } } } } fun initiatePurchaseFlow(activity: Activity, sku: String) { executeServiceRequest { val params = BillingFlowParams.newBuilder().setSku(sku).setType(SkuType.INAPP) billingClient.launchBillingFlow(activity, params.build()) } } private fun executeServiceRequest(runnable: () -> Unit) { when (isServiceConnected) { true -> runnable() false -> startServiceConnection(runnable) } } override fun onPurchasesUpdated(resultCode: Int, purchases: List<Purchase>?) { if (resultCode == BillingResponse.OK) { purchaseListObservable.onNext(purchases.orEmpty()) } } } Loading
build.gradle +1 −1 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ buildscript { ext.exoplayer_version = "2.8.1" ext.glide_version = "4.8.0" ext.junit_version = '4.12' ext.kotlin_version = '1.3.31' ext.kotlin_version = '1.3.50' ext.lifecycle_version = '2.1.0' ext.material_version = '1.0.0' ext.mockito_version = '2.18.3' Loading
presentation/build.gradle +1 −0 Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ android { signingConfigs.release.keyAlias = System.getenv("key_alias") signingConfigs.release.keyPassword = System.getenv("key_password") } } androidExtensions { Loading
presentation/src/main/AndroidManifest.xml +2 −2 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- <?xml version="1.0" encoding="utf-8"?><!-- ~ Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com> ~ ~ This file is part of QKSMS. Loading Loading @@ -94,6 +93,7 @@ android:value=".common.util.QkChooserTargetService" /> </activity> <activity android:name=".feature.settings.SettingsActivity" /> <activity android:name=".feature.plus.PlusActivity" /> <activity android:name=".feature.gallery.GalleryActivity" android:theme="@style/AppTheme.Black" /> Loading
presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt +12 −0 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import com.moez.QKSMS.feature.compose.ComposeActivity import com.moez.QKSMS.feature.conversationinfo.ConversationInfoActivity import com.moez.QKSMS.feature.gallery.GalleryActivity import com.moez.QKSMS.feature.notificationprefs.NotificationPrefsActivity import com.moez.QKSMS.feature.plus.PlusActivity import com.moez.QKSMS.feature.scheduled.ScheduledActivity import com.moez.QKSMS.feature.settings.SettingsActivity import com.moez.QKSMS.manager.AnalyticsManager Loading @@ -52,6 +53,7 @@ class Navigator @Inject constructor( private val context: Context, private val analyticsManager: AnalyticsManager, private val notificationManager: NotificationManager, private val billingManager: BillingManager, private val permissions: PermissionManager ) { Loading Loading @@ -149,6 +151,16 @@ class Navigator @Inject constructor( startActivityExternal(intent) } fun showFork() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms")) startActivity(intent) } fun showCopyright() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://gitlab.e.foundation/e/apps/message/blob/e-features/AUTHORS")) startActivity(intent) } fun showChangelog() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms/releases")) startActivityExternal(intent) Loading
presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com> * * This file is part of QKSMS. * * QKSMS 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 * (at your option) any later version. * * QKSMS 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 QKSMS. If not, see <http://www.gnu.org/licenses/>. */ package com.moez.QKSMS.common.util import android.app.Activity import android.content.Context import com.android.billingclient.api.BillingClient import com.android.billingclient.api.BillingClient.BillingResponse import com.android.billingclient.api.BillingClient.SkuType import com.android.billingclient.api.BillingClientStateListener import com.android.billingclient.api.BillingFlowParams import com.android.billingclient.api.Purchase import com.android.billingclient.api.PurchasesUpdatedListener import com.android.billingclient.api.SkuDetails import com.android.billingclient.api.SkuDetailsParams import com.moez.QKSMS.BuildConfig import com.moez.QKSMS.manager.AnalyticsManager import io.reactivex.Flowable import io.reactivex.Observable import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @Singleton class BillingManager @Inject constructor( context: Context, private val analyticsManager: AnalyticsManager ) : PurchasesUpdatedListener { companion object { const val SKU_PLUS = "remove_ads" const val SKU_PLUS_DONATE = "qksms_plus_donate" } val products: Observable<List<SkuDetails>> = BehaviorSubject.create() val upgradeStatus: Observable<Boolean> private val skus = listOf(SKU_PLUS, SKU_PLUS_DONATE) private val purchaseListObservable = BehaviorSubject.create<List<Purchase>>() private val billingClient: BillingClient = BillingClient.newBuilder(context).setListener(this).build() private var isServiceConnected = false init { startServiceConnection { queryPurchases() querySkuDetailsAsync() } upgradeStatus = when (BuildConfig.FLAVOR) { "noAnalytics" -> BehaviorSubject.createDefault(true) else -> purchaseListObservable .map { purchases -> purchases.any { it.sku == SKU_PLUS } || purchases.any { it.sku == SKU_PLUS_DONATE } } .doOnNext { upgraded -> analyticsManager.setUserProperty("Upgraded", upgraded) } } } private fun queryPurchases() { executeServiceRequest { // Load the cached data purchaseListObservable.onNext(billingClient.queryPurchases(SkuType.INAPP).purchasesList.orEmpty()) // On a fresh device, the purchase might not be cached, and so we'll need to force a refresh billingClient.queryPurchaseHistoryAsync(SkuType.INAPP) { _, _ -> purchaseListObservable.onNext(billingClient.queryPurchases(SkuType.INAPP).purchasesList.orEmpty()) } } } private fun startServiceConnection(onSuccess: () -> Unit) { val listener = object : BillingClientStateListener { override fun onBillingSetupFinished(@BillingResponse billingResponseCode: Int) { if (billingResponseCode == BillingResponse.OK) { isServiceConnected = true onSuccess() } else { Timber.w("Billing response: $billingResponseCode") purchaseListObservable.onNext(listOf()) } } override fun onBillingServiceDisconnected() { isServiceConnected = false } } Flowable.fromCallable { billingClient.startConnection(listener) } .subscribeOn(Schedulers.io()) .subscribe() } private fun querySkuDetailsAsync() { executeServiceRequest { val subParams = SkuDetailsParams.newBuilder().setSkusList(skus).setType(BillingClient.SkuType.INAPP) billingClient.querySkuDetailsAsync(subParams.build()) { responseCode, skuDetailsList -> if (responseCode == BillingResponse.OK) { (products as Subject).onNext(skuDetailsList) } } } } fun initiatePurchaseFlow(activity: Activity, sku: String) { executeServiceRequest { val params = BillingFlowParams.newBuilder().setSku(sku).setType(SkuType.INAPP) billingClient.launchBillingFlow(activity, params.build()) } } private fun executeServiceRequest(runnable: () -> Unit) { when (isServiceConnected) { true -> runnable() false -> startServiceConnection(runnable) } } override fun onPurchasesUpdated(resultCode: Int, purchases: List<Purchase>?) { if (resultCode == BillingResponse.OK) { purchaseListObservable.onNext(purchases.orEmpty()) } } }