Loading data/src/main/java/com/moez/QKSMS/util/NightModeManager.kt +87 −7 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ */ package com.moez.QKSMS.util import android.app.AlarmManager import android.app.PendingIntent import android.content.Context Loading @@ -39,16 +40,95 @@ class NightModeManager @Inject constructor( ) { fun updateCurrentTheme() { when (prefs.nightMode.get()) { Preferences.NIGHT_MODE_SYSTEM -> { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) } Preferences.NIGHT_MODE_OFF -> { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) } Preferences.NIGHT_MODE_ON -> { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) } Preferences.NIGHT_MODE_AUTO -> { val nightStartTime = getPreviousInstanceOfTime(prefs.nightStart.get()) val nightEndTime = getPreviousInstanceOfTime(prefs.nightEnd.get()) // If the last nightStart was more recent than the last nightEnd, then it's night time val night = nightStartTime > nightEndTime prefs.night.set(night) AppCompatDelegate.setDefaultNightMode(when (night) { true -> AppCompatDelegate.MODE_NIGHT_YES false -> AppCompatDelegate.MODE_NIGHT_NO }) widgetManager.updateTheme() } } } fun updateNightMode(mode: Int) { prefs.nightMode.set(Preferences.NIGHT_MODE_SYSTEM) prefs.night.set(false) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) prefs.nightMode.set(mode) // If it's not on auto mode, set the appropriate night mode if (mode != Preferences.NIGHT_MODE_AUTO) { prefs.night.set(mode == Preferences.NIGHT_MODE_ON) AppCompatDelegate.setDefaultNightMode(when (mode) { Preferences.NIGHT_MODE_OFF -> AppCompatDelegate.MODE_NIGHT_NO Preferences.NIGHT_MODE_ON -> AppCompatDelegate.MODE_NIGHT_YES Preferences.NIGHT_MODE_SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM else -> AppCompatDelegate.MODE_NIGHT_NO }) widgetManager.updateTheme() } updateAlarms() } fun setNightStart(hour: Int, minute: Int) { prefs.nightStart.set("$hour:$minute") updateAlarms() } fun setNightEnd(hour: Int, minute: Int) { prefs.nightEnd.set("$hour:$minute") updateAlarms() } private fun updateAlarms() { val dayCalendar = createCalendar(prefs.nightEnd.get()) val day = Intent(context, NightModeReceiver::class.java) val dayIntent = PendingIntent.getBroadcast(context, 0, day, 0) val nightCalendar = createCalendar(prefs.nightStart.get()) val night = Intent(context, NightModeReceiver::class.java) val nightIntent = PendingIntent.getBroadcast(context, 1, night, 0) context.sendBroadcast(day) context.sendBroadcast(night) val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager if (prefs.nightMode.get() == Preferences.NIGHT_MODE_AUTO) { alarmManager.setInexactRepeating( AlarmManager.RTC_WAKEUP, dayCalendar.timeInMillis, AlarmManager.INTERVAL_DAY, dayIntent ) alarmManager.setInexactRepeating( AlarmManager.RTC_WAKEUP, nightCalendar.timeInMillis, AlarmManager.INTERVAL_DAY, nightIntent ) } else { alarmManager.cancel(dayIntent) alarmManager.cancel(nightIntent) } } private fun createCalendar(time: String): Calendar { val calendar = parseTime(time) Loading presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt +25 −11 Original line number Diff line number Diff line Loading @@ -52,8 +52,8 @@ import javax.inject.Singleton class Navigator @Inject constructor( private val context: Context, private val analyticsManager: AnalyticsManager, private val notificationManager: NotificationManager, private val billingManager: BillingManager, private val notificationManager: NotificationManager, private val permissions: PermissionManager ) { Loading Loading @@ -141,9 +141,24 @@ class Navigator @Inject constructor( startActivity(intent) } fun openUri(uri: Uri) { val intent = Intent(Intent.ACTION_VIEW, uri) startActivity(intent) fun showDeveloper() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti")) startActivityExternal(intent) } fun showSourceCode() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms")) startActivityExternal(intent) } fun showChangelog() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms/releases")) startActivityExternal(intent) } fun showLicense() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms/blob/master/LICENSE")) startActivityExternal(intent) } fun showBlockedConversations() { Loading Loading @@ -277,6 +292,7 @@ class Navigator @Inject constructor( if (threadId != 0L) { notificationManager.createNotificationChannel(threadId) } val channelId = notificationManager.buildNotificationChannelId(threadId) val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) .putExtra(Settings.EXTRA_CHANNEL_ID, channelId) Loading @@ -285,6 +301,4 @@ class Navigator @Inject constructor( } } } No newline at end of file presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt +15 −112 Original line number Diff line number Diff line /* * Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com> * Copyright (C) 2020 Moez Bhatti <moez.bhatti@gmail.com> * * This file is part of QKSMS. * Loading @@ -16,129 +16,32 @@ * 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 package com.moez.QKSMS.manager 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 { interface BillingManager { 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()) data class Product( val sku: String, val price: String, val priceCurrencyCode: String ) // 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) } } } } val products: Observable<List<Product>> val upgradeStatus: Observable<Boolean> fun initiatePurchaseFlow(activity: Activity, sku: String) { executeServiceRequest { val params = BillingFlowParams.newBuilder().setSku(sku).setType(SkuType.INAPP) billingClient.launchBillingFlow(activity, params.build()) } } suspend fun checkForPurchases() private fun executeServiceRequest(runnable: () -> Unit) { when (isServiceConnected) { true -> runnable() false -> startServiceConnection(runnable) } } suspend fun queryProducts() override fun onPurchasesUpdated(resultCode: Int, purchases: List<Purchase>?) { if (resultCode == BillingResponse.OK) { purchaseListObservable.onNext(purchases.orEmpty()) } } suspend fun initiatePurchaseFlow(activity: Activity, sku: String) } No newline at end of file presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +0 −1 Original line number Diff line number Diff line Loading @@ -80,7 +80,6 @@ class AvatarView @JvmOverloads constructor( super.onFinishInflate() if (!isInEditMode) { applyTheme(threadId) updateView() } } Loading presentation/src/main/java/com/moez/QKSMS/common/widget/TightTextView.kt +1 −1 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ class TightTextView @JvmOverloads constructor( val maxLineWidth = (0 until layout.lineCount) .map(layout::getLineWidth) .max() ?: 0f .maxOrNull() ?: 0f val width = Math.ceil(maxLineWidth.toDouble()).toInt() + compoundPaddingLeft + compoundPaddingRight if (width < measuredWidth) { Loading Loading
data/src/main/java/com/moez/QKSMS/util/NightModeManager.kt +87 −7 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ */ package com.moez.QKSMS.util import android.app.AlarmManager import android.app.PendingIntent import android.content.Context Loading @@ -39,16 +40,95 @@ class NightModeManager @Inject constructor( ) { fun updateCurrentTheme() { when (prefs.nightMode.get()) { Preferences.NIGHT_MODE_SYSTEM -> { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) } Preferences.NIGHT_MODE_OFF -> { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) } Preferences.NIGHT_MODE_ON -> { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) } Preferences.NIGHT_MODE_AUTO -> { val nightStartTime = getPreviousInstanceOfTime(prefs.nightStart.get()) val nightEndTime = getPreviousInstanceOfTime(prefs.nightEnd.get()) // If the last nightStart was more recent than the last nightEnd, then it's night time val night = nightStartTime > nightEndTime prefs.night.set(night) AppCompatDelegate.setDefaultNightMode(when (night) { true -> AppCompatDelegate.MODE_NIGHT_YES false -> AppCompatDelegate.MODE_NIGHT_NO }) widgetManager.updateTheme() } } } fun updateNightMode(mode: Int) { prefs.nightMode.set(Preferences.NIGHT_MODE_SYSTEM) prefs.night.set(false) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) prefs.nightMode.set(mode) // If it's not on auto mode, set the appropriate night mode if (mode != Preferences.NIGHT_MODE_AUTO) { prefs.night.set(mode == Preferences.NIGHT_MODE_ON) AppCompatDelegate.setDefaultNightMode(when (mode) { Preferences.NIGHT_MODE_OFF -> AppCompatDelegate.MODE_NIGHT_NO Preferences.NIGHT_MODE_ON -> AppCompatDelegate.MODE_NIGHT_YES Preferences.NIGHT_MODE_SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM else -> AppCompatDelegate.MODE_NIGHT_NO }) widgetManager.updateTheme() } updateAlarms() } fun setNightStart(hour: Int, minute: Int) { prefs.nightStart.set("$hour:$minute") updateAlarms() } fun setNightEnd(hour: Int, minute: Int) { prefs.nightEnd.set("$hour:$minute") updateAlarms() } private fun updateAlarms() { val dayCalendar = createCalendar(prefs.nightEnd.get()) val day = Intent(context, NightModeReceiver::class.java) val dayIntent = PendingIntent.getBroadcast(context, 0, day, 0) val nightCalendar = createCalendar(prefs.nightStart.get()) val night = Intent(context, NightModeReceiver::class.java) val nightIntent = PendingIntent.getBroadcast(context, 1, night, 0) context.sendBroadcast(day) context.sendBroadcast(night) val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager if (prefs.nightMode.get() == Preferences.NIGHT_MODE_AUTO) { alarmManager.setInexactRepeating( AlarmManager.RTC_WAKEUP, dayCalendar.timeInMillis, AlarmManager.INTERVAL_DAY, dayIntent ) alarmManager.setInexactRepeating( AlarmManager.RTC_WAKEUP, nightCalendar.timeInMillis, AlarmManager.INTERVAL_DAY, nightIntent ) } else { alarmManager.cancel(dayIntent) alarmManager.cancel(nightIntent) } } private fun createCalendar(time: String): Calendar { val calendar = parseTime(time) Loading
presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt +25 −11 Original line number Diff line number Diff line Loading @@ -52,8 +52,8 @@ import javax.inject.Singleton class Navigator @Inject constructor( private val context: Context, private val analyticsManager: AnalyticsManager, private val notificationManager: NotificationManager, private val billingManager: BillingManager, private val notificationManager: NotificationManager, private val permissions: PermissionManager ) { Loading Loading @@ -141,9 +141,24 @@ class Navigator @Inject constructor( startActivity(intent) } fun openUri(uri: Uri) { val intent = Intent(Intent.ACTION_VIEW, uri) startActivity(intent) fun showDeveloper() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti")) startActivityExternal(intent) } fun showSourceCode() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms")) startActivityExternal(intent) } fun showChangelog() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms/releases")) startActivityExternal(intent) } fun showLicense() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms/blob/master/LICENSE")) startActivityExternal(intent) } fun showBlockedConversations() { Loading Loading @@ -277,6 +292,7 @@ class Navigator @Inject constructor( if (threadId != 0L) { notificationManager.createNotificationChannel(threadId) } val channelId = notificationManager.buildNotificationChannelId(threadId) val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) .putExtra(Settings.EXTRA_CHANNEL_ID, channelId) Loading @@ -285,6 +301,4 @@ class Navigator @Inject constructor( } } } No newline at end of file
presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt +15 −112 Original line number Diff line number Diff line /* * Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com> * Copyright (C) 2020 Moez Bhatti <moez.bhatti@gmail.com> * * This file is part of QKSMS. * Loading @@ -16,129 +16,32 @@ * 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 package com.moez.QKSMS.manager 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 { interface BillingManager { 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()) data class Product( val sku: String, val price: String, val priceCurrencyCode: String ) // 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) } } } } val products: Observable<List<Product>> val upgradeStatus: Observable<Boolean> fun initiatePurchaseFlow(activity: Activity, sku: String) { executeServiceRequest { val params = BillingFlowParams.newBuilder().setSku(sku).setType(SkuType.INAPP) billingClient.launchBillingFlow(activity, params.build()) } } suspend fun checkForPurchases() private fun executeServiceRequest(runnable: () -> Unit) { when (isServiceConnected) { true -> runnable() false -> startServiceConnection(runnable) } } suspend fun queryProducts() override fun onPurchasesUpdated(resultCode: Int, purchases: List<Purchase>?) { if (resultCode == BillingResponse.OK) { purchaseListObservable.onNext(purchases.orEmpty()) } } suspend fun initiatePurchaseFlow(activity: Activity, sku: String) } No newline at end of file
presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +0 −1 Original line number Diff line number Diff line Loading @@ -80,7 +80,6 @@ class AvatarView @JvmOverloads constructor( super.onFinishInflate() if (!isInEditMode) { applyTheme(threadId) updateView() } } Loading
presentation/src/main/java/com/moez/QKSMS/common/widget/TightTextView.kt +1 −1 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ class TightTextView @JvmOverloads constructor( val maxLineWidth = (0 until layout.lineCount) .map(layout::getLineWidth) .max() ?: 0f .maxOrNull() ?: 0f val width = Math.ceil(maxLineWidth.toDouble()).toInt() + compoundPaddingLeft + compoundPaddingRight if (width < measuredWidth) { Loading