diff --git a/app/src/main/java/com/afollestad/nocknock/broadcasts/StatusUpdateIntentReceiver.kt b/app/src/main/java/com/afollestad/nocknock/broadcasts/StatusUpdateIntentReceiver.kt index 9a507d7..c5567c5 100644 --- a/app/src/main/java/com/afollestad/nocknock/broadcasts/StatusUpdateIntentReceiver.kt +++ b/app/src/main/java/com/afollestad/nocknock/broadcasts/StatusUpdateIntentReceiver.kt @@ -15,11 +15,10 @@ */ package com.afollestad.nocknock.broadcasts -import android.app.Application import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.content.IntentFilter +import androidx.lifecycle.Lifecycle.Event.ON_DESTROY import androidx.lifecycle.Lifecycle.Event.ON_PAUSE import androidx.lifecycle.Lifecycle.Event.ON_RESUME import androidx.lifecycle.LifecycleObserver @@ -27,13 +26,15 @@ import androidx.lifecycle.OnLifecycleEvent import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.ACTION_STATUS_UPDATE import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.KEY_UPDATE_MODEL +import com.afollestad.nocknock.utilities.providers.IntentProvider typealias SiteCallback = (Site) -> Unit /** @author Aidan Follestad (@afollestad) */ class StatusUpdateIntentReceiver( - private val app: Application, - private val callback: SiteCallback + private val context: Context, + private val intentProvider: IntentProvider, + private var callback: SiteCallback? ) : LifecycleObserver { internal val intentReceiver = object : BroadcastReceiver() { @@ -44,19 +45,24 @@ class StatusUpdateIntentReceiver( if (intent.action == ACTION_STATUS_UPDATE) { val model = intent.getSerializableExtra(KEY_UPDATE_MODEL) as? Site ?: return - callback(model) + callback?.invoke(model) } } } @OnLifecycleEvent(ON_RESUME) fun onResume() { - val filter = IntentFilter().apply { - addAction(ACTION_STATUS_UPDATE) - } - app.registerReceiver(intentReceiver, filter) + val filter = intentProvider.createFilter(ACTION_STATUS_UPDATE) + context.registerReceiver(intentReceiver, filter) } @OnLifecycleEvent(ON_PAUSE) - fun onPause() = app.unregisterReceiver(intentReceiver) + fun onPause() { + context.unregisterReceiver(intentReceiver) + } + + @OnLifecycleEvent(ON_DESTROY) + fun onDestroy() { + callback = null + } } diff --git a/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivity.kt index 2fc867a..4ee0e75 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivity.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivity.kt @@ -30,6 +30,7 @@ import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.dialogs.AboutDialog import com.afollestad.nocknock.notifications.NockNotificationManager +import com.afollestad.nocknock.utilities.providers.IntentProvider import com.afollestad.nocknock.viewcomponents.ext.showOrHide import kotlinx.android.synthetic.main.activity_main.fab import kotlinx.android.synthetic.main.activity_main.list @@ -43,12 +44,14 @@ import org.koin.androidx.viewmodel.ext.android.viewModel class MainActivity : AppCompatActivity() { private val notificationManager by inject() + private val intentProvider by inject() + internal val viewModel by viewModel() private lateinit var adapter: ServerAdapter private val statusUpdateReceiver = - StatusUpdateIntentReceiver(application) { + StatusUpdateIntentReceiver(application, intentProvider) { viewModel.postSiteUpdate(it) } diff --git a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt index aace599..c4d9ad4 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt @@ -25,6 +25,7 @@ import com.afollestad.nocknock.R import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.data.model.ValidationMode +import com.afollestad.nocknock.utilities.providers.IntentProvider import com.afollestad.nocknock.viewcomponents.ext.attachLiveData import com.afollestad.nocknock.viewcomponents.ext.dimenFloat import com.afollestad.nocknock.viewcomponents.ext.onScroll @@ -48,6 +49,7 @@ import kotlinx.android.synthetic.main.activity_viewsite.textNextCheck import kotlinx.android.synthetic.main.activity_viewsite.textUrlWarning import kotlinx.android.synthetic.main.activity_viewsite.toolbar import kotlinx.android.synthetic.main.activity_viewsite.validationModeDescription +import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel /** @author Aidan Follestad (@afollestad) */ @@ -55,8 +57,9 @@ class ViewSiteActivity : AppCompatActivity() { internal val viewModel by viewModel() + private val intentProvider by inject() private val statusUpdateReceiver = - StatusUpdateIntentReceiver(application) { + StatusUpdateIntentReceiver(application, intentProvider) { viewModel.setModel(it) } diff --git a/app/src/test/java/com/afollestad/nocknock/TestData.kt b/app/src/test/java/com/afollestad/nocknock/TestData.kt index aad071f..ebe713d 100644 --- a/app/src/test/java/com/afollestad/nocknock/TestData.kt +++ b/app/src/test/java/com/afollestad/nocknock/TestData.kt @@ -15,7 +15,9 @@ */ package com.afollestad.nocknock +import android.app.PendingIntent import android.content.Intent +import android.content.IntentFilter import com.afollestad.nocknock.data.AppDatabase import com.afollestad.nocknock.data.SiteDao import com.afollestad.nocknock.data.SiteSettingsDao @@ -27,6 +29,9 @@ import com.afollestad.nocknock.data.model.Status.OK import com.afollestad.nocknock.data.model.ValidationMode import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE import com.afollestad.nocknock.data.model.ValidationResult +import com.afollestad.nocknock.utilities.providers.CanNotifyModel +import com.afollestad.nocknock.utilities.providers.IntentProvider +import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.doAnswer import com.nhaarman.mockitokotlin2.doReturn import com.nhaarman.mockitokotlin2.isA @@ -130,3 +135,21 @@ fun mockDatabase(): AppDatabase { on { validationResultsDao() } doReturn resultsDao } } + +fun mockIntentProvider() = object : IntentProvider { + override fun createFilter(vararg actions: String): IntentFilter { + return mock { + on { this.getAction(any()) } doAnswer { inv -> + val index = inv.getArgument(0) + return@doAnswer actions[index] + } + on { this.actionsIterator() } doReturn actions.iterator() + on { this.countActions() } doReturn actions.size + } + } + + override fun getPendingIntentForViewSite(model: CanNotifyModel): PendingIntent { + // basically no-op right now + return mock() + } +} diff --git a/app/src/test/java/com/afollestad/nocknock/broadcasts/StatusUpdateIntentReceiverTest.kt b/app/src/test/java/com/afollestad/nocknock/broadcasts/StatusUpdateIntentReceiverTest.kt index 0d9b88b..e3e51b3 100644 --- a/app/src/test/java/com/afollestad/nocknock/broadcasts/StatusUpdateIntentReceiverTest.kt +++ b/app/src/test/java/com/afollestad/nocknock/broadcasts/StatusUpdateIntentReceiverTest.kt @@ -21,9 +21,11 @@ import com.afollestad.nocknock.MOCK_MODEL_2 import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.ACTION_STATUS_UPDATE import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.KEY_UPDATE_MODEL import com.afollestad.nocknock.fakeIntent +import com.afollestad.nocknock.mockIntentProvider import com.google.common.truth.Truth.assertThat import com.nhaarman.mockitokotlin2.argumentCaptor import com.nhaarman.mockitokotlin2.doReturn +import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.times import com.nhaarman.mockitokotlin2.verify @@ -34,9 +36,10 @@ import org.junit.Test class StatusUpdateIntentReceiverTest { private val app = mock() + private val intentProvider = mockIntentProvider() private val callback = mock() - private val receiver = StatusUpdateIntentReceiver(app, callback) + private val receiver = StatusUpdateIntentReceiver(app, intentProvider, callback) @Test fun onReceive() { val badIntent = fakeIntent("Hello World") @@ -54,7 +57,7 @@ class StatusUpdateIntentReceiverTest { receiver.onResume() val filterCaptor = argumentCaptor() - verify(app).registerReceiver(receiver.intentReceiver, filterCaptor.capture()) + verify(app).registerReceiver(eq(receiver.intentReceiver), filterCaptor.capture()) val actionIterator = filterCaptor.firstValue.actionsIterator() assertThat(actionIterator.hasNext()).isTrue() diff --git a/common/src/main/java/com/afollestad/nocknock/utilities/providers/IntentProvider.kt b/common/src/main/java/com/afollestad/nocknock/utilities/providers/IntentProvider.kt index 3592bbe..a177c49 100644 --- a/common/src/main/java/com/afollestad/nocknock/utilities/providers/IntentProvider.kt +++ b/common/src/main/java/com/afollestad/nocknock/utilities/providers/IntentProvider.kt @@ -19,21 +19,24 @@ import android.app.PendingIntent import android.app.PendingIntent.FLAG_CANCEL_CURRENT import android.content.Context import android.content.Intent +import android.content.IntentFilter import java.io.Serializable /** @author Aidan Follestad (@afollestad) */ interface CanNotifyModel : Serializable { - fun notiId(): Int + fun notifyId(): Int - fun notiName(): String + fun notifyName(): String - fun notiTag(): String + fun notifyTag(): String } /** @author Aidan Follestad (@afollestad) */ interface IntentProvider { + fun createFilter(vararg actions: String): IntentFilter + fun getPendingIntentForViewSite( model: CanNotifyModel ): PendingIntent @@ -50,11 +53,15 @@ class RealIntentProvider( const val KEY_VIEW_NOTIFICATION_MODEL = "model" } + override fun createFilter(vararg actions: String) = IntentFilter().apply { + actions.forEach { addAction(it) } + } + override fun getPendingIntentForViewSite(model: CanNotifyModel): PendingIntent { val openIntent = getIntentForViewSite(model) return PendingIntent.getActivity( context, - BASE_NOTIFICATION_REQUEST_CODE + model.notiId(), + BASE_NOTIFICATION_REQUEST_CODE + model.notifyId(), openIntent, FLAG_CANCEL_CURRENT ) diff --git a/data/src/main/java/com/afollestad/nocknock/data/model/Site.kt b/data/src/main/java/com/afollestad/nocknock/data/model/Site.kt index b0539d7..132cc4e 100644 --- a/data/src/main/java/com/afollestad/nocknock/data/model/Site.kt +++ b/data/src/main/java/com/afollestad/nocknock/data/model/Site.kt @@ -41,11 +41,11 @@ data class Site( constructor() : this(0, "", "", null, null) - override fun notiId(): Int = id.toInt() + override fun notifyId(): Int = id.toInt() - override fun notiName(): String = name + override fun notifyName(): String = name - override fun notiTag(): String = url + override fun notifyTag(): String = url fun intervalText(): String { requireNotNull(settings) { "Settings not queried." } diff --git a/notifications/src/main/java/com/afollestad/nocknock/notifications/NockNotificationManager.kt b/notifications/src/main/java/com/afollestad/nocknock/notifications/NockNotificationManager.kt index 5be2912..981a442 100644 --- a/notifications/src/main/java/com/afollestad/nocknock/notifications/NockNotificationManager.kt +++ b/notifications/src/main/java/com/afollestad/nocknock/notifications/NockNotificationManager.kt @@ -66,29 +66,29 @@ class RealNockNotificationManager( override fun postStatusNotification(model: CanNotifyModel) { if (isAppOpen) { // Don't show notifications while the app is open - log("App is open, status notification for site ${model.notiId()} won't be posted.") + log("App is open, status notification for site ${model.notifyId()} won't be posted.") return } - log("Posting status notification for site ${model.notiId()}...") + log("Posting status notification for site ${model.notifyId()}...") val intent = intentProvider.getPendingIntentForViewSite(model) val newNotification = notificationProvider.create( channelId = CheckFailures.id, - title = model.notiName(), + title = model.notifyName(), content = stringProvider.get(R.string.something_wrong), intent = intent, smallIcon = R.drawable.ic_notification, largeIcon = bitmapProvider.get(appIconRes) ) - stockManager.notify(model.notiTag(), model.notificationId(), newNotification) + stockManager.notify(model.notifyTag(), model.notificationId(), newNotification) log("Posted status notification for site ${model.notificationId()}.") } override fun cancelStatusNotification(model: CanNotifyModel) { stockManager.cancel(model.notificationId()) - log("Cancelled status notification for site ${model.notiId()}.") + log("Cancelled status notification for site ${model.notifyId()}.") } override fun cancelStatusNotifications() = stockManager.cancelAll() @@ -105,5 +105,5 @@ class RealNockNotificationManager( log("Created notification channel ${channel.id}") } - private fun CanNotifyModel.notificationId() = BASE_NOTIFICATION_REQUEST_CODE + this.notiId() + private fun CanNotifyModel.notificationId() = BASE_NOTIFICATION_REQUEST_CODE + this.notifyId() }