diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt b/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt similarity index 86% rename from presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt rename to common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt index cdfdee0a..d1df3568 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt +++ b/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Moez Bhatti + * Copyright (C) 2019 Moez Bhatti * * This file is part of QKSMS. * @@ -18,11 +18,9 @@ */ package com.moez.QKSMS.common.util.extensions -import android.app.Activity import android.content.Context import android.graphics.Color import android.util.TypedValue -import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.annotation.StringRes import androidx.core.content.ContextCompat @@ -65,15 +63,6 @@ fun Context.makeToast(text: String, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(this, text, duration).show() } -fun Activity.dismissKeyboard() { - window.currentFocus?.let { focus -> - val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(focus.windowToken, 0) - - focus.clearFocus() - } -} - fun Context.isInstalled(packageName: String): Boolean { return tryOrNull(false) { packageManager.getApplicationInfo(packageName, 0).enabled } ?: false } diff --git a/data/src/main/java/com/moez/QKSMS/util/Utils.kt b/common/src/main/java/com/moez/QKSMS/util/Utils.kt similarity index 93% rename from data/src/main/java/com/moez/QKSMS/util/Utils.kt rename to common/src/main/java/com/moez/QKSMS/util/Utils.kt index bea95400..5d6fd155 100644 --- a/data/src/main/java/com/moez/QKSMS/util/Utils.kt +++ b/common/src/main/java/com/moez/QKSMS/util/Utils.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Moez Bhatti + * Copyright (C) 2019 Moez Bhatti * * This file is part of QKSMS. * @@ -30,4 +30,4 @@ fun tryOrNull(logOnError: Boolean = true, body: () -> T?): T? { null } -} \ No newline at end of file +} diff --git a/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt b/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt index 0b4c9078..ca64fa9f 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt @@ -32,6 +32,8 @@ class BlockingManager @Inject constructor( } } + override fun isAvailable(): Boolean = client.isAvailable() + override fun isBlocked(address: String): Single = client.isBlocked(address) override fun canBlock(): Boolean = client.canBlock() diff --git a/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt index 2b7a3c4a..d5242079 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt @@ -24,6 +24,7 @@ import android.database.Cursor import android.net.Uri import androidx.core.database.getStringOrNull import com.callcontrol.datashare.CallControl +import com.moez.QKSMS.common.util.extensions.isInstalled import com.moez.QKSMS.extensions.map import com.moez.QKSMS.util.tryOrNull import io.reactivex.Completable @@ -43,6 +44,8 @@ class CallControlBlockingClient @Inject constructor( val blockReason: String? = cursor.getStringOrNull(0) } + override fun isAvailable(): Boolean = context.isInstalled("com.flexaspect.android.everycallcontrol") + override fun isBlocked(address: String): Single { val uri = Uri.withAppendedPath(CallControl.LOOKUP_TEXT_URI, address) return Single.fromCallable { diff --git a/data/src/main/java/com/moez/QKSMS/blocking/QkBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/QkBlockingClient.kt index 12032e55..9a4d1517 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/QkBlockingClient.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/QkBlockingClient.kt @@ -34,6 +34,8 @@ class QkBlockingClient @Inject constructor( private val blockingRepo: BlockingRepository ) : BlockingClient { + override fun isAvailable(): Boolean = true + override fun isBlocked(address: String): Single = Single.fromCallable { when { Build.VERSION.SDK_INT >= 24 -> BlockedNumberContract.isBlocked(context, address) diff --git a/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt index d803d0da..29dbafe4 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt @@ -28,6 +28,7 @@ import android.os.IBinder import android.os.Message import android.os.Messenger import androidx.core.os.bundleOf +import com.moez.QKSMS.common.util.extensions.isInstalled import com.moez.QKSMS.util.tryOrNull import io.reactivex.Completable import io.reactivex.Single @@ -47,6 +48,11 @@ class ShouldIAnswerBlockingClient @Inject constructor( const val GET_NUMBER_RATING = 1 } + override fun isAvailable(): Boolean = listOf("org.mistergroup.shouldianswer", + "org.mistergroup.shouldianswerpersonal", + "org.mistergroup.muzutozvednout") + .any(context::isInstalled) + /** * Return a Single which emits whether or not the given [address] should be blocked */ diff --git a/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt b/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt index c1c584ee..47d203fd 100644 --- a/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt +++ b/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt @@ -23,6 +23,11 @@ import io.reactivex.Single interface BlockingClient { + /** + * Returns true if the target blocking client is available for use, ie. it is installed + */ + fun isAvailable(): Boolean + /** * Return a Single which emits whether or not the given [address] should be blocked */ diff --git a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt index 5b50ff0d..a3f08daf 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt @@ -183,7 +183,7 @@ class Navigator @Inject constructor( /** * Launch the Play Store and display the Call Control listing */ - fun showCallControl() { + fun installCallControl() { val url = "https://play.google.com/store/apps/details?id=com.flexaspect.android.everycallcontrol" val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) startActivity(intent) @@ -192,7 +192,7 @@ class Navigator @Inject constructor( /** * Launch the Play Store and display the Should I Answer? listing */ - fun showSia() { + fun installSia() { val url = "https://play.google.com/store/apps/details?id=org.mistergroup.shouldianswer" val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) startActivity(intent) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt new file mode 100644 index 00000000..83c103ad --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * 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 . + */ +package com.moez.QKSMS.common.util.extensions + +import android.app.Activity +import android.content.Context +import android.view.inputmethod.InputMethodManager + +fun Activity.dismissKeyboard() { + window.currentFocus?.let { focus -> + val imm = getSystemService( + Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(focus.windowToken, 0) + + focus.clearFocus() + } +} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/RadioPreferenceView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/RadioPreferenceView.kt index 61d2ed28..20c4da43 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/RadioPreferenceView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/RadioPreferenceView.kt @@ -88,6 +88,12 @@ class RadioPreferenceView @JvmOverloads constructor( context.obtainStyledAttributes(attrs, R.styleable.RadioPreferenceView)?.run { title = getString(R.styleable.RadioPreferenceView_title) summary = getString(R.styleable.RadioPreferenceView_summary) + + // If there's a custom view used for the preference's widget, inflate it + getResourceId(R.styleable.RadioPreferenceView_widget, -1).takeIf { it != -1 }?.let { id -> + View.inflate(context, id, widgetFrame) + } + recycle() } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt index dd5b7658..fac50ca9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt @@ -27,9 +27,7 @@ import com.moez.QKSMS.common.QkChangeHandler import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.animateLayoutChanges -import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.feature.blocking.manager.BlockingManagerController -import com.moez.QKSMS.feature.blocking.numbers.BlockedNumbersController import com.moez.QKSMS.injection.appComponent import kotlinx.android.synthetic.main.blocking_controller.* import kotlinx.android.synthetic.main.settings_switch_widget.view.* @@ -38,7 +36,6 @@ import javax.inject.Inject class BlockingController : QkController(), BlockingView { override val blockingManagerIntent by lazy { blockingManager.clicks() } - override val settingsClicks by lazy { settings.clicks() } override val dropClickedIntent by lazy { drop.clicks() } override val conversationClicks by lazy { blockingAdapter.clicks } @@ -55,7 +52,6 @@ class BlockingController : QkController { val blockingManagerIntent: Observable<*> - val settingsClicks: Observable<*> val dropClickedIntent: Observable<*> val conversationClicks: Observable fun openBlockingManager() - fun openBlockedNumbers() } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerController.kt index 3647d7c7..01f09cbc 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerController.kt @@ -1,13 +1,19 @@ package com.moez.QKSMS.feature.blocking.manager +import android.app.Activity import android.view.View +import com.bluelinelabs.conductor.RouterTransaction import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R +import com.moez.QKSMS.common.QkChangeHandler import com.moez.QKSMS.common.base.QkController +import com.moez.QKSMS.feature.blocking.numbers.BlockedNumbersController import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.util.Preferences import io.reactivex.Observable +import io.reactivex.subjects.PublishSubject import kotlinx.android.synthetic.main.blocking_manager_controller.* +import kotlinx.android.synthetic.main.blocking_manager_list_option.view.* import kotlinx.android.synthetic.main.radio_preference_view.view.* import javax.inject.Inject @@ -16,6 +22,8 @@ class BlockingManagerController : QkController = PublishSubject.create() + init { appComponent.inject(this) retainViewMode = RetainViewMode.RETAIN_DETACH @@ -29,14 +37,38 @@ class BlockingManagerController : QkController R.drawable.ic_chevron_right_black_24dp + false -> R.drawable.ic_info_black_24dp + }) + + shouldIAnswer.radioButton.isChecked = state.blockingManager == Preferences.BLOCKING_MANAGER_SIA + shouldIAnswer.launch.setImageResource(when (state.siaInstalled) { + true -> R.drawable.ic_chevron_right_black_24dp + false -> R.drawable.ic_info_black_24dp + }) + } + + override fun activityResumed(): Observable<*> = activityResumedSubject override fun qksmsClicked(): Observable<*> = qksms.clicks() + override fun launchQksmsClicked(): Observable<*> = qksms.launch.clicks() override fun callControlClicked(): Observable<*> = callControl.clicks() + override fun launchCallControlClicked(): Observable<*> = callControl.launch.clicks() override fun siaClicked(): Observable<*> = shouldIAnswer.clicks() + override fun launchSiaClicked(): Observable<*> = shouldIAnswer.launch.clicks() + + override fun openBlockedNumbers() { + router.pushController(RouterTransaction.with(BlockedNumbersController()) + .pushChangeHandler(QkChangeHandler()) + .popChangeHandler(QkChangeHandler())) + } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt index d366c56a..71026e93 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt @@ -1,10 +1,11 @@ package com.moez.QKSMS.feature.blocking.manager -import android.content.Context +import android.os.Build import com.moez.QKSMS.blocking.CallControlBlockingClient +import com.moez.QKSMS.blocking.QkBlockingClient +import com.moez.QKSMS.blocking.ShouldIAnswerBlockingClient import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkPresenter -import com.moez.QKSMS.common.util.extensions.isInstalled import com.moez.QKSMS.manager.AnalyticsManager import com.moez.QKSMS.util.Preferences import com.uber.autodispose.kotlin.autoDisposable @@ -14,10 +15,15 @@ import javax.inject.Inject class BlockingManagerPresenter @Inject constructor( private val analytics: AnalyticsManager, private val callControl: CallControlBlockingClient, - private val context: Context, private val navigator: Navigator, - private val prefs: Preferences -) : QkPresenter(BlockingManagerState()) { + private val prefs: Preferences, + private val qksms: QkBlockingClient, + private val shouldIAnswer: ShouldIAnswerBlockingClient +) : QkPresenter(BlockingManagerState( + blockingManager = prefs.blockingManager.get(), + callControlInstalled = callControl.isAvailable(), + siaInstalled = shouldIAnswer.isAvailable() +)) { init { disposables += prefs.blockingManager.asObservable() @@ -27,6 +33,18 @@ class BlockingManagerPresenter @Inject constructor( override fun bindIntents(view: BlockingManagerView) { super.bindIntents(view) + view.activityResumed() + .map { callControl.isAvailable() } + .distinctUntilChanged() + .autoDisposable(view.scope()) + .subscribe { available -> newState { copy(callControlInstalled = available) } } + + view.activityResumed() + .map { shouldIAnswer.isAvailable() } + .distinctUntilChanged() + .autoDisposable(view.scope()) + .subscribe { available -> newState { copy(siaInstalled = available) } } + view.qksmsClicked() .autoDisposable(view.scope()) .subscribe { @@ -34,12 +52,23 @@ class BlockingManagerPresenter @Inject constructor( prefs.blockingManager.set(Preferences.BLOCKING_MANAGER_QKSMS) } + view.launchQksmsClicked() + .autoDisposable(view.scope()) + .subscribe { + // TODO: This is a hack, get rid of it once we implement AndroidX navigation + if (Build.VERSION.SDK_INT < 24) { + view.openBlockedNumbers() + } else { + qksms.openSettings() + } + } + view.callControlClicked() .filter { - val installed = context.isInstalled("com.flexaspect.android.everycallcontrol") + val installed = callControl.isAvailable() if (!installed) { analytics.track("Install Call Control") - navigator.showCallControl() + navigator.installCallControl() } val enabled = prefs.blockingManager.get() == Preferences.BLOCKING_MANAGER_CC @@ -52,16 +81,21 @@ class BlockingManagerPresenter @Inject constructor( prefs.blockingManager.set(Preferences.BLOCKING_MANAGER_CC) } + view.launchCallControlClicked() + .autoDisposable(view.scope()) + .subscribe { + when (callControl.isAvailable()) { + true -> callControl.openSettings() + false -> navigator.installCallControl() + } + } + view.siaClicked() .filter { - val installed = listOf("org.mistergroup.shouldianswer", - "org.mistergroup.shouldianswerpersonal", - "org.mistergroup.muzutozvednout") - .any(context::isInstalled) - + val installed = shouldIAnswer.isAvailable() if (!installed) { analytics.track("Install SIA") - navigator.showSia() + navigator.installSia() } val enabled = prefs.blockingManager.get() == Preferences.BLOCKING_MANAGER_SIA @@ -72,6 +106,15 @@ class BlockingManagerPresenter @Inject constructor( analytics.setUserProperty("Blocking Manager", "SIA") prefs.blockingManager.set(Preferences.BLOCKING_MANAGER_SIA) } + + view.launchSiaClicked() + .autoDisposable(view.scope()) + .subscribe { + when (shouldIAnswer.isAvailable()) { + true -> shouldIAnswer.openSettings() + false -> navigator.installSia() + } + } } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerState.kt index f1719f91..2663caf0 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerState.kt @@ -1,5 +1,7 @@ package com.moez.QKSMS.feature.blocking.manager data class BlockingManagerState( - val blockingManager: Int = 0 + val blockingManager: Int = 0, + val callControlInstalled: Boolean = false, + val siaInstalled: Boolean = false ) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerView.kt index 956bacab..72e8af0f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerView.kt @@ -5,8 +5,14 @@ import io.reactivex.Observable interface BlockingManagerView : QkViewContract { + fun activityResumed(): Observable<*> fun qksmsClicked(): Observable<*> + fun launchQksmsClicked(): Observable<*> fun callControlClicked(): Observable<*> + fun launchCallControlClicked(): Observable<*> fun siaClicked(): Observable<*> + fun launchSiaClicked(): Observable<*> + + fun openBlockedNumbers() } diff --git a/presentation/src/main/res/layout/blocking_controller.xml b/presentation/src/main/res/layout/blocking_controller.xml index ea9cd511..40222811 100644 --- a/presentation/src/main/res/layout/blocking_controller.xml +++ b/presentation/src/main/res/layout/blocking_controller.xml @@ -34,39 +34,13 @@ - - - - + app:title="@string/app_name" + app:widget="@layout/blocking_manager_list_option" /> + app:title="@string/blocking_manager_call_control_title" + app:widget="@layout/blocking_manager_list_option" /> + app:title="@string/blocking_manager_sia_title" + app:widget="@layout/blocking_manager_list_option" /> diff --git a/presentation/src/main/res/layout/blocking_manager_list_option.xml b/presentation/src/main/res/layout/blocking_manager_list_option.xml new file mode 100644 index 00000000..1220959b --- /dev/null +++ b/presentation/src/main/res/layout/blocking_manager_list_option.xml @@ -0,0 +1,28 @@ + + + diff --git a/presentation/src/main/res/layout/radio_preference_view.xml b/presentation/src/main/res/layout/radio_preference_view.xml index d96c79d5..8a1d2a67 100644 --- a/presentation/src/main/res/layout/radio_preference_view.xml +++ b/presentation/src/main/res/layout/radio_preference_view.xml @@ -39,11 +39,12 @@ @@ -60,4 +61,14 @@ app:layout_constraintTop_toBottomOf="@id/titleView" tools:text="QKSMS is your default SMS app" /> + + diff --git a/presentation/src/main/res/values/attrs.xml b/presentation/src/main/res/values/attrs.xml index d308658b..2e430b5b 100644 --- a/presentation/src/main/res/values/attrs.xml +++ b/presentation/src/main/res/values/attrs.xml @@ -21,6 +21,7 @@ + @@ -50,13 +51,14 @@ - + + \ No newline at end of file diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index a9c579e0..727fd85f 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -252,7 +252,6 @@ Enter duration (seconds) Blocking - Open settings for selected blocking manager Drop messages Drop incoming messages from blocked senders instead of hiding them Blocked conversations