Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b390d492 authored by Romain Hunault's avatar Romain Hunault 💻
Browse files

Merge branch 'dev-tmp' into 'dev'

Fix #14 Remove "Do you like QKSMS?"
Fix #16 Backup not working (disabled)
Fix #13 Replace QKSMS by Message

See merge request e/apps/Message!6
parents 4638c70f 9cd97a73
Loading
Loading
Loading
Loading
Loading
+0 −62
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.mapper

import com.f2prateek.rx.preferences2.RxSharedPreferences
import com.moez.QKSMS.manager.AnalyticsManager
import com.moez.QKSMS.manager.RatingManager
import io.reactivex.rxkotlin.Observables
import javax.inject.Inject

class RatingManagerImpl @Inject constructor(
    rxPrefs: RxSharedPreferences,
    private val analyticsManager: AnalyticsManager
) : RatingManager {

    companion object {
        private const val RATING_THRESHOLD = 10
    }

    private val sessions = rxPrefs.getInteger("sessions", 0)
    private val rated = rxPrefs.getBoolean("rated", false)
    private val dismissed = rxPrefs.getBoolean("dismissed", false)

    override val shouldShowRating = Observables.combineLatest(
            sessions.asObservable(),
            rated.asObservable(),
            dismissed.asObservable()
    ) { sessions, rated, dismissed ->
        sessions > RATING_THRESHOLD && !rated && !dismissed
    }

    override fun addSession() {
        sessions.set(sessions.get() + 1)
    }

    override fun rate() {
        analyticsManager.track("Clicked Rate")
        rated.set(true)
    }

    override fun dismiss() {
        analyticsManager.track("Clicked Rate (Dismiss)")
        dismissed.set(true)
    }

}
+0 −36
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.manager

import io.reactivex.Observable

interface RatingManager {

    val shouldShowRating: Observable<Boolean>

    /**
     * Whether or not we should show the rating UI should depend on the number of sessions
     */
    fun addSession()

    fun rate()

    fun dismiss()

}
 No newline at end of file
+0 −1
Original line number Diff line number Diff line
@@ -93,7 +93,6 @@
                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/AppThemeBlack" />
+0 −3
Original line number Diff line number Diff line
@@ -26,8 +26,6 @@ import android.os.Build
import android.provider.ContactsContract
import android.provider.Settings
import android.provider.Telephony
import com.moez.QKSMS.BuildConfig
import com.moez.QKSMS.common.util.BillingManager
import com.moez.QKSMS.feature.backup.BackupActivity
import com.moez.QKSMS.feature.blocked.BlockedActivity
import com.moez.QKSMS.feature.compose.ComposeActivity
@@ -47,7 +45,6 @@ import javax.inject.Singleton
class Navigator @Inject constructor(
    private val context: Context,
    private val analyticsManager: AnalyticsManager,
    private val billingManager: BillingManager,
    private val notificationManager: NotificationManager,
    private val permissions: PermissionManager
) {
+0 −144
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())
        }
    }

}
 No newline at end of file
Loading