mirror of
https://github.com/afollestad/nock-nock.git
synced 2025-08-03 06:38:38 +00:00
Bug fixes, UI improvements, fix notifications, etc.
This commit is contained in:
parent
b750957d79
commit
08ddf1ca03
22 changed files with 371 additions and 203 deletions
|
@ -9,6 +9,8 @@ import android.app.Activity
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.app.Application.ActivityLifecycleCallbacks
|
import android.app.Application.ActivityLifecycleCallbacks
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||||
|
import androidx.core.text.HtmlCompat.fromHtml
|
||||||
|
|
||||||
typealias ActivityLifeChange = (activity: Activity, resumed: Boolean) -> Unit
|
typealias ActivityLifeChange = (activity: Activity, resumed: Boolean) -> Unit
|
||||||
|
|
||||||
|
@ -36,3 +38,5 @@ fun Application.onActivityLifeChange(cb: ActivityLifeChange) {
|
||||||
) = Unit
|
) = Unit
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.toHtml() = fromHtml(this, FROM_HTML_MODE_LEGACY)
|
||||||
|
|
|
@ -6,7 +6,9 @@
|
||||||
package com.afollestad.nocknock.di
|
package com.afollestad.nocknock.di
|
||||||
|
|
||||||
import com.afollestad.nocknock.R
|
import com.afollestad.nocknock.R
|
||||||
|
import com.afollestad.nocknock.ui.main.MainActivity
|
||||||
import com.afollestad.nocknock.utilities.qualifiers.AppIconRes
|
import com.afollestad.nocknock.utilities.qualifiers.AppIconRes
|
||||||
|
import com.afollestad.nocknock.utilities.qualifiers.MainActivityClass
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -19,4 +21,9 @@ open class MainModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
@AppIconRes
|
@AppIconRes
|
||||||
fun provideAppIconRes(): Int = R.mipmap.ic_launcher
|
fun provideAppIconRes(): Int = R.mipmap.ic_launcher
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@MainActivityClass
|
||||||
|
fun provideMainActivityClass(): Class<*> = MainActivity::class.java
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,7 @@
|
||||||
package com.afollestad.nocknock.ui.addsite
|
package com.afollestad.nocknock.ui.addsite
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
|
||||||
import android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.ViewAnimationUtils.createCircularReveal
|
|
||||||
import android.view.animation.AccelerateInterpolator
|
|
||||||
import android.view.animation.DecelerateInterpolator
|
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.afollestad.nocknock.R
|
import com.afollestad.nocknock.R
|
||||||
|
@ -20,15 +15,12 @@ import com.afollestad.nocknock.data.ValidationMode.JAVASCRIPT
|
||||||
import com.afollestad.nocknock.data.ValidationMode.STATUS_CODE
|
import com.afollestad.nocknock.data.ValidationMode.STATUS_CODE
|
||||||
import com.afollestad.nocknock.data.ValidationMode.TERM_SEARCH
|
import com.afollestad.nocknock.data.ValidationMode.TERM_SEARCH
|
||||||
import com.afollestad.nocknock.data.indexToValidationMode
|
import com.afollestad.nocknock.data.indexToValidationMode
|
||||||
import com.afollestad.nocknock.ui.main.MainActivity
|
|
||||||
import com.afollestad.nocknock.utilities.ext.ScopeReceiver
|
import com.afollestad.nocknock.utilities.ext.ScopeReceiver
|
||||||
import com.afollestad.nocknock.utilities.ext.injector
|
import com.afollestad.nocknock.utilities.ext.injector
|
||||||
import com.afollestad.nocknock.utilities.ext.onEnd
|
|
||||||
import com.afollestad.nocknock.utilities.ext.scopeWhileAttached
|
import com.afollestad.nocknock.utilities.ext.scopeWhileAttached
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.conceal
|
import com.afollestad.nocknock.viewcomponents.ext.conceal
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.onItemSelected
|
import com.afollestad.nocknock.viewcomponents.ext.onItemSelected
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.onLayout
|
import com.afollestad.nocknock.viewcomponents.ext.onLayout
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.show
|
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.showOrHide
|
import com.afollestad.nocknock.viewcomponents.ext.showOrHide
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.trimmedText
|
import com.afollestad.nocknock.viewcomponents.ext.trimmedText
|
||||||
import kotlinx.android.synthetic.main.activity_addsite.checkIntervalLayout
|
import kotlinx.android.synthetic.main.activity_addsite.checkIntervalLayout
|
||||||
|
@ -48,37 +40,20 @@ import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.properties.Delegates.notNull
|
import kotlin.properties.Delegates.notNull
|
||||||
|
|
||||||
private const val KEY_FAB_X = "fab_x"
|
const val KEY_FAB_X = "fab_x"
|
||||||
private const val KEY_FAB_Y = "fab_y"
|
const val KEY_FAB_Y = "fab_y"
|
||||||
private const val KEY_FAB_SIZE = "fab_size"
|
const val KEY_FAB_SIZE = "fab_size"
|
||||||
|
|
||||||
/** @author Aidan Follestad (@afollestad) */
|
|
||||||
fun MainActivity.intentToAdd(
|
|
||||||
x: Float,
|
|
||||||
y: Float,
|
|
||||||
size: Int
|
|
||||||
) = Intent(this, AddSiteActivity::class.java).apply {
|
|
||||||
putExtra(KEY_FAB_X, x)
|
|
||||||
putExtra(KEY_FAB_Y, y)
|
|
||||||
putExtra(KEY_FAB_SIZE, size)
|
|
||||||
addFlags(FLAG_ACTIVITY_NO_ANIMATION)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @author Aidan Follestad (@afollestad) */
|
/** @author Aidan Follestad (@afollestad) */
|
||||||
class AddSiteActivity : AppCompatActivity(), AddSiteView {
|
class AddSiteActivity : AppCompatActivity(), AddSiteView {
|
||||||
|
|
||||||
companion object {
|
var isClosing: Boolean = false
|
||||||
private const val REVEAL_DURATION = 300L
|
var revealCx by notNull<Int>()
|
||||||
}
|
var revealCy by notNull<Int>()
|
||||||
|
var revealRadius by notNull<Float>()
|
||||||
private var isClosing: Boolean = false
|
|
||||||
|
|
||||||
@Inject lateinit var presenter: AddSitePresenter
|
@Inject lateinit var presenter: AddSitePresenter
|
||||||
|
|
||||||
private var revealCx by notNull<Int>()
|
|
||||||
private var revealCy by notNull<Int>()
|
|
||||||
private var revealRadius by notNull<Float>()
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -203,33 +178,6 @@ class AddSiteActivity : AppCompatActivity(), AddSiteView {
|
||||||
exec: ScopeReceiver
|
exec: ScopeReceiver
|
||||||
) = rootView.scopeWhileAttached(context, exec)
|
) = rootView.scopeWhileAttached(context, exec)
|
||||||
|
|
||||||
private fun circularRevealActivity() {
|
|
||||||
val circularReveal =
|
|
||||||
createCircularReveal(rootView, revealCx, revealCy, 0f, revealRadius)
|
|
||||||
.apply {
|
|
||||||
duration = REVEAL_DURATION
|
|
||||||
interpolator = DecelerateInterpolator()
|
|
||||||
}
|
|
||||||
rootView.show()
|
|
||||||
circularReveal.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun closeActivityWithReveal() {
|
|
||||||
if (isClosing) return
|
|
||||||
isClosing = true
|
|
||||||
createCircularReveal(rootView, revealCx, revealCy, revealRadius, 0f)
|
|
||||||
.apply {
|
|
||||||
duration = REVEAL_DURATION
|
|
||||||
interpolator = AccelerateInterpolator()
|
|
||||||
onEnd {
|
|
||||||
rootView.conceal()
|
|
||||||
finish()
|
|
||||||
overridePendingTransition(0, 0)
|
|
||||||
}
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBackPressed() = closeActivityWithReveal()
|
override fun onBackPressed() = closeActivityWithReveal()
|
||||||
|
|
||||||
private fun ValidationMode.validationContent() = when (this) {
|
private fun ValidationMode.validationContent() = when (this) {
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Licensed under Apache-2.0
|
||||||
|
*
|
||||||
|
* Designed and developed by Aidan Follestad (@afollestad)
|
||||||
|
*/
|
||||||
|
package com.afollestad.nocknock.ui.addsite
|
||||||
|
|
||||||
|
import android.view.ViewAnimationUtils
|
||||||
|
import android.view.animation.AccelerateInterpolator
|
||||||
|
import android.view.animation.DecelerateInterpolator
|
||||||
|
import com.afollestad.nocknock.utilities.ext.onEnd
|
||||||
|
import com.afollestad.nocknock.viewcomponents.ext.conceal
|
||||||
|
import com.afollestad.nocknock.viewcomponents.ext.show
|
||||||
|
import kotlinx.android.synthetic.main.activity_addsite.rootView
|
||||||
|
|
||||||
|
const val REVEAL_DURATION = 300L
|
||||||
|
|
||||||
|
internal fun AddSiteActivity.circularRevealActivity() {
|
||||||
|
val circularReveal =
|
||||||
|
ViewAnimationUtils.createCircularReveal(rootView, revealCx, revealCy, 0f, revealRadius)
|
||||||
|
.apply {
|
||||||
|
duration = REVEAL_DURATION
|
||||||
|
interpolator = DecelerateInterpolator()
|
||||||
|
}
|
||||||
|
rootView.show()
|
||||||
|
circularReveal.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun AddSiteActivity.closeActivityWithReveal() {
|
||||||
|
if (isClosing) return
|
||||||
|
isClosing = true
|
||||||
|
ViewAnimationUtils.createCircularReveal(rootView, revealCx, revealCy, revealRadius, 0f)
|
||||||
|
.apply {
|
||||||
|
duration = REVEAL_DURATION
|
||||||
|
interpolator = AccelerateInterpolator()
|
||||||
|
onEnd {
|
||||||
|
rootView.conceal()
|
||||||
|
finish()
|
||||||
|
overridePendingTransition(0, 0)
|
||||||
|
}
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,15 +5,12 @@
|
||||||
*/
|
*/
|
||||||
package com.afollestad.nocknock.ui.main
|
package com.afollestad.nocknock.ui.main
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
|
|
||||||
import androidx.core.text.HtmlCompat.fromHtml
|
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
|
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
@ -24,8 +21,6 @@ import com.afollestad.nocknock.adapter.ServerAdapter
|
||||||
import com.afollestad.nocknock.data.ServerModel
|
import com.afollestad.nocknock.data.ServerModel
|
||||||
import com.afollestad.nocknock.dialogs.AboutDialog
|
import com.afollestad.nocknock.dialogs.AboutDialog
|
||||||
import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.ACTION_STATUS_UPDATE
|
import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.ACTION_STATUS_UPDATE
|
||||||
import com.afollestad.nocknock.ui.addsite.intentToAdd
|
|
||||||
import com.afollestad.nocknock.ui.viewsite.intentToView
|
|
||||||
import com.afollestad.nocknock.utilities.ext.ScopeReceiver
|
import com.afollestad.nocknock.utilities.ext.ScopeReceiver
|
||||||
import com.afollestad.nocknock.utilities.ext.injector
|
import com.afollestad.nocknock.utilities.ext.injector
|
||||||
import com.afollestad.nocknock.utilities.ext.safeRegisterReceiver
|
import com.afollestad.nocknock.utilities.ext.safeRegisterReceiver
|
||||||
|
@ -43,11 +38,6 @@ import kotlin.coroutines.CoroutineContext
|
||||||
/** @author Aidan Follestad (@afollestad) */
|
/** @author Aidan Follestad (@afollestad) */
|
||||||
class MainActivity : AppCompatActivity(), MainView {
|
class MainActivity : AppCompatActivity(), MainView {
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val ADD_SITE_RQ = 6969
|
|
||||||
private const val VIEW_SITE_RQ = 6923
|
|
||||||
}
|
|
||||||
|
|
||||||
private val intentReceiver = object : BroadcastReceiver() {
|
private val intentReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(
|
override fun onReceive(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
@ -59,7 +49,6 @@ class MainActivity : AppCompatActivity(), MainView {
|
||||||
|
|
||||||
private lateinit var adapter: ServerAdapter
|
private lateinit var adapter: ServerAdapter
|
||||||
|
|
||||||
@SuppressLint("CommitPrefEdits")
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
@ -81,12 +70,14 @@ class MainActivity : AppCompatActivity(), MainView {
|
||||||
list.adapter = adapter
|
list.adapter = adapter
|
||||||
list.addItemDecoration(DividerItemDecoration(this, VERTICAL))
|
list.addItemDecoration(DividerItemDecoration(this, VERTICAL))
|
||||||
|
|
||||||
fab.setOnClickListener {
|
fab.setOnClickListener { addSite() }
|
||||||
startActivityForResult(
|
|
||||||
intentToAdd(fab.x, fab.y, fab.measuredWidth),
|
processIntent(intent)
|
||||||
ADD_SITE_RQ
|
}
|
||||||
)
|
|
||||||
}
|
override fun onNewIntent(intent: Intent?) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
intent?.let(::processIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -145,25 +136,8 @@ class MainActivity : AppCompatActivity(), MainView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
} else {
|
||||||
}
|
viewSite(model)
|
||||||
|
|
||||||
startActivityForResult(intentToView(model), VIEW_SITE_RQ)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun maybeRemoveSite(model: ServerModel) {
|
|
||||||
MaterialDialog(this).show {
|
|
||||||
title(R.string.remove_site)
|
|
||||||
message(
|
|
||||||
text = fromHtml(
|
|
||||||
context.getString(R.string.remove_site_prompt, model.name),
|
|
||||||
FROM_HTML_MODE_LEGACY
|
|
||||||
)
|
|
||||||
)
|
|
||||||
positiveButton(R.string.remove) {
|
|
||||||
presenter.removeSite(model)
|
|
||||||
}
|
|
||||||
negativeButton(android.R.string.cancel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Licensed under Apache-2.0
|
||||||
|
*
|
||||||
|
* Designed and developed by Aidan Follestad (@afollestad)
|
||||||
|
*/
|
||||||
|
package com.afollestad.nocknock.ui.main
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import com.afollestad.nocknock.R
|
||||||
|
import com.afollestad.nocknock.data.ServerModel
|
||||||
|
import com.afollestad.nocknock.toHtml
|
||||||
|
import com.afollestad.nocknock.ui.addsite.AddSiteActivity
|
||||||
|
import com.afollestad.nocknock.ui.addsite.KEY_FAB_SIZE
|
||||||
|
import com.afollestad.nocknock.ui.addsite.KEY_FAB_X
|
||||||
|
import com.afollestad.nocknock.ui.addsite.KEY_FAB_Y
|
||||||
|
import com.afollestad.nocknock.ui.viewsite.KEY_VIEW_MODEL
|
||||||
|
import com.afollestad.nocknock.ui.viewsite.ViewSiteActivity
|
||||||
|
import com.afollestad.nocknock.utilities.providers.RealIntentProvider.Companion.KEY_VIEW_NOTIFICATION_MODEL
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.fab
|
||||||
|
|
||||||
|
internal const val VIEW_SITE_RQ = 6923
|
||||||
|
internal const val ADD_SITE_RQ = 6969
|
||||||
|
|
||||||
|
internal fun MainActivity.addSite() {
|
||||||
|
startActivityForResult(intentToAdd(fab.x, fab.y, fab.measuredWidth), ADD_SITE_RQ)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MainActivity.intentToAdd(
|
||||||
|
x: Float,
|
||||||
|
y: Float,
|
||||||
|
size: Int
|
||||||
|
) = Intent(this, AddSiteActivity::class.java).apply {
|
||||||
|
putExtra(KEY_FAB_X, x)
|
||||||
|
putExtra(KEY_FAB_Y, y)
|
||||||
|
putExtra(KEY_FAB_SIZE, size)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun MainActivity.viewSite(model: ServerModel) {
|
||||||
|
startActivityForResult(intentToView(model), VIEW_SITE_RQ)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MainActivity.intentToView(model: ServerModel) =
|
||||||
|
Intent(this, ViewSiteActivity::class.java).apply {
|
||||||
|
putExtra(KEY_VIEW_MODEL, model)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun MainActivity.maybeRemoveSite(model: ServerModel) {
|
||||||
|
MaterialDialog(this).show {
|
||||||
|
title(R.string.remove_site)
|
||||||
|
message(text = context.getString(R.string.remove_site_prompt, model.name).toHtml())
|
||||||
|
positiveButton(R.string.remove) { presenter.removeSite(model) }
|
||||||
|
negativeButton(android.R.string.cancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun MainActivity.processIntent(intent: Intent) {
|
||||||
|
if (intent.hasExtra(KEY_VIEW_NOTIFICATION_MODEL)) {
|
||||||
|
val model = intent.getSerializableExtra(KEY_VIEW_NOTIFICATION_MODEL) as ServerModel
|
||||||
|
viewSite(model)
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,14 +13,9 @@ import android.content.IntentFilter
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.text.HtmlCompat
|
|
||||||
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
|
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
|
||||||
import com.afollestad.nocknock.R
|
import com.afollestad.nocknock.R
|
||||||
import com.afollestad.nocknock.data.LAST_CHECK_NONE
|
import com.afollestad.nocknock.data.LAST_CHECK_NONE
|
||||||
import com.afollestad.nocknock.data.ServerModel
|
import com.afollestad.nocknock.data.ServerModel
|
||||||
import com.afollestad.nocknock.data.ServerStatus.CHECKING
|
|
||||||
import com.afollestad.nocknock.data.ServerStatus.WAITING
|
|
||||||
import com.afollestad.nocknock.data.ValidationMode
|
import com.afollestad.nocknock.data.ValidationMode
|
||||||
import com.afollestad.nocknock.data.ValidationMode.JAVASCRIPT
|
import com.afollestad.nocknock.data.ValidationMode.JAVASCRIPT
|
||||||
import com.afollestad.nocknock.data.ValidationMode.STATUS_CODE
|
import com.afollestad.nocknock.data.ValidationMode.STATUS_CODE
|
||||||
|
@ -28,7 +23,6 @@ import com.afollestad.nocknock.data.ValidationMode.TERM_SEARCH
|
||||||
import com.afollestad.nocknock.data.indexToValidationMode
|
import com.afollestad.nocknock.data.indexToValidationMode
|
||||||
import com.afollestad.nocknock.data.textRes
|
import com.afollestad.nocknock.data.textRes
|
||||||
import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.ACTION_STATUS_UPDATE
|
import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.ACTION_STATUS_UPDATE
|
||||||
import com.afollestad.nocknock.ui.main.MainActivity
|
|
||||||
import com.afollestad.nocknock.utilities.ext.ScopeReceiver
|
import com.afollestad.nocknock.utilities.ext.ScopeReceiver
|
||||||
import com.afollestad.nocknock.utilities.ext.formatDate
|
import com.afollestad.nocknock.utilities.ext.formatDate
|
||||||
import com.afollestad.nocknock.utilities.ext.injector
|
import com.afollestad.nocknock.utilities.ext.injector
|
||||||
|
@ -60,12 +54,6 @@ import kotlinx.android.synthetic.main.activity_viewsite.validationModeDescriptio
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
/** @author Aidan Follestad (@afollestad) */
|
|
||||||
fun MainActivity.intentToView(model: ServerModel) =
|
|
||||||
Intent(this, ViewSiteActivity::class.java).apply {
|
|
||||||
putExtra(KEY_VIEW_MODEL, model)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @author Aidan Follestad (@afollestad) */
|
/** @author Aidan Follestad (@afollestad) */
|
||||||
class ViewSiteActivity : AppCompatActivity(), ViewSiteView {
|
class ViewSiteActivity : AppCompatActivity(), ViewSiteView {
|
||||||
|
|
||||||
|
@ -88,11 +76,13 @@ class ViewSiteActivity : AppCompatActivity(), ViewSiteView {
|
||||||
toolbar.run {
|
toolbar.run {
|
||||||
setNavigationOnClickListener { finish() }
|
setNavigationOnClickListener { finish() }
|
||||||
inflateMenu(R.menu.menu_viewsite)
|
inflateMenu(R.menu.menu_viewsite)
|
||||||
|
menu.findItem(R.id.refresh)
|
||||||
|
.setActionView(R.layout.menu_item_refresh_icon)
|
||||||
|
.apply {
|
||||||
|
actionView.setOnClickListener { presenter.checkNow() }
|
||||||
|
}
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener {
|
||||||
when (it.itemId) {
|
maybeRemoveSite()
|
||||||
R.id.refresh -> presenter.checkNow()
|
|
||||||
R.id.remove -> maybeRemoveSite()
|
|
||||||
}
|
|
||||||
return@setOnMenuItemClickListener true
|
return@setOnMenuItemClickListener true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,7 +180,6 @@ class ViewSiteActivity : AppCompatActivity(), ViewSiteView {
|
||||||
checkIntervalLayout.set(this.checkInterval)
|
checkIntervalLayout.set(this.checkInterval)
|
||||||
|
|
||||||
responseValidationMode.setSelection(validationMode.value - 1)
|
responseValidationMode.setSelection(validationMode.value - 1)
|
||||||
|
|
||||||
when (this.validationMode) {
|
when (this.validationMode) {
|
||||||
TERM_SEARCH -> responseValidationSearchTerm.setText(this.validationContent ?: "")
|
TERM_SEARCH -> responseValidationSearchTerm.setText(this.validationContent ?: "")
|
||||||
JAVASCRIPT -> scriptInputLayout.setCode(this.validationContent)
|
JAVASCRIPT -> scriptInputLayout.setCode(this.validationContent)
|
||||||
|
@ -206,7 +195,7 @@ class ViewSiteActivity : AppCompatActivity(), ViewSiteView {
|
||||||
else R.string.save_changes
|
else R.string.save_changes
|
||||||
)
|
)
|
||||||
|
|
||||||
invalidateMenuForStatus()
|
invalidateMenuForStatus(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setInputErrors(errors: InputErrors) {
|
override fun setInputErrors(errors: InputErrors) {
|
||||||
|
@ -259,43 +248,6 @@ class ViewSiteActivity : AppCompatActivity(), ViewSiteView {
|
||||||
safeUnregisterReceiver(intentReceiver)
|
safeUnregisterReceiver(intentReceiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeRemoveSite() {
|
|
||||||
val model = presenter.currentModel()
|
|
||||||
MaterialDialog(this).show {
|
|
||||||
title(R.string.remove_site)
|
|
||||||
message(
|
|
||||||
text = HtmlCompat.fromHtml(
|
|
||||||
context.getString(R.string.remove_site_prompt, model.name),
|
|
||||||
FROM_HTML_MODE_LEGACY
|
|
||||||
)
|
|
||||||
)
|
|
||||||
positiveButton(R.string.remove) { presenter.removeSite() }
|
|
||||||
negativeButton(android.R.string.cancel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun maybeDisableChecks() {
|
|
||||||
val model = presenter.currentModel()
|
|
||||||
MaterialDialog(this).show {
|
|
||||||
title(R.string.disable_automatic_checks)
|
|
||||||
message(
|
|
||||||
text = HtmlCompat.fromHtml(
|
|
||||||
context.getString(R.string.disable_automatic_checks_prompt, model.name),
|
|
||||||
FROM_HTML_MODE_LEGACY
|
|
||||||
)
|
|
||||||
)
|
|
||||||
positiveButton(R.string.disable) { presenter.disableChecks() }
|
|
||||||
negativeButton(android.R.string.cancel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun invalidateMenuForStatus() {
|
|
||||||
val model = presenter.currentModel()
|
|
||||||
val item = toolbar.menu.findItem(R.id.refresh)
|
|
||||||
item.isEnabled = model.status != CHECKING &&
|
|
||||||
model.status != WAITING
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ValidationMode.validationContent() = when (this) {
|
private fun ValidationMode.validationContent() = when (this) {
|
||||||
STATUS_CODE -> null
|
STATUS_CODE -> null
|
||||||
TERM_SEARCH -> responseValidationSearchTerm.trimmedText()
|
TERM_SEARCH -> responseValidationSearchTerm.trimmedText()
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Licensed under Apache-2.0
|
||||||
|
*
|
||||||
|
* Designed and developed by Aidan Follestad (@afollestad)
|
||||||
|
*/
|
||||||
|
package com.afollestad.nocknock.ui.viewsite
|
||||||
|
|
||||||
|
import android.widget.ImageView
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import com.afollestad.nocknock.R
|
||||||
|
import com.afollestad.nocknock.data.ServerModel
|
||||||
|
import com.afollestad.nocknock.data.isPending
|
||||||
|
import com.afollestad.nocknock.toHtml
|
||||||
|
import com.afollestad.nocknock.utilities.ext.animateRotation
|
||||||
|
import kotlinx.android.synthetic.main.activity_viewsite.toolbar
|
||||||
|
|
||||||
|
internal fun ViewSiteActivity.maybeRemoveSite() {
|
||||||
|
val model = presenter.currentModel()
|
||||||
|
MaterialDialog(this).show {
|
||||||
|
title(R.string.remove_site)
|
||||||
|
message(text = context.getString(R.string.remove_site_prompt, model.name).toHtml())
|
||||||
|
positiveButton(R.string.remove) { presenter.removeSite() }
|
||||||
|
negativeButton(android.R.string.cancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ViewSiteActivity.maybeDisableChecks() {
|
||||||
|
val model = presenter.currentModel()
|
||||||
|
MaterialDialog(this).show {
|
||||||
|
title(R.string.disable_automatic_checks)
|
||||||
|
message(
|
||||||
|
text = context.getString(R.string.disable_automatic_checks_prompt, model.name).toHtml()
|
||||||
|
)
|
||||||
|
positiveButton(R.string.disable) { presenter.disableChecks() }
|
||||||
|
negativeButton(android.R.string.cancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ViewSiteActivity.invalidateMenuForStatus(model: ServerModel) {
|
||||||
|
val refreshIcon = toolbar.menu.findItem(R.id.refresh)
|
||||||
|
.actionView as ImageView
|
||||||
|
|
||||||
|
if (model.status.isPending()) {
|
||||||
|
refreshIcon.animateRotation()
|
||||||
|
} else {
|
||||||
|
refreshIcon.run {
|
||||||
|
animate().cancel()
|
||||||
|
rotation = 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -104,10 +104,7 @@ class RealViewSitePresenter @Inject constructor(
|
||||||
if (intent.action == ACTION_STATUS_UPDATE) {
|
if (intent.action == ACTION_STATUS_UPDATE) {
|
||||||
val model = intent.getSerializableExtra(KEY_UPDATE_MODEL) as? ServerModel ?: return
|
val model = intent.getSerializableExtra(KEY_UPDATE_MODEL) as? ServerModel ?: return
|
||||||
this.currentModel = model
|
this.currentModel = model
|
||||||
view?.run {
|
view?.displayModel(model)
|
||||||
displayModel(model)
|
|
||||||
setDoneLoading() // in case this is the result of a manual refresh
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +205,6 @@ class RealViewSitePresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkNow() = with(view!!) {
|
override fun checkNow() = with(view!!) {
|
||||||
setLoading()
|
|
||||||
val checkModel = currentModel!!.copy(
|
val checkModel = currentModel!!.copy(
|
||||||
status = WAITING
|
status = WAITING
|
||||||
)
|
)
|
||||||
|
|
9
app/src/main/res/layout/menu_item_refresh_icon.xml
Normal file
9
app/src/main/res/layout/menu_item_refresh_icon.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ImageView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/refresh_status"
|
||||||
|
android:src="@drawable/ic_action_refresh"
|
||||||
|
style="@android:style/Widget.ActionButton"
|
||||||
|
/>
|
|
@ -88,7 +88,6 @@ class ViewSitePresenterTest {
|
||||||
presenter.onBroadcast(goodIntent)
|
presenter.onBroadcast(goodIntent)
|
||||||
assertThat(presenter.currentModel()).isEqualTo(model)
|
assertThat(presenter.currentModel()).isEqualTo(model)
|
||||||
verify(view, times(1)).displayModel(model)
|
verify(view, times(1)).displayModel(model)
|
||||||
verify(view).setDoneLoading()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun onNewIntent() {
|
@Test fun onNewIntent() {
|
||||||
|
@ -308,7 +307,7 @@ class ViewSitePresenterTest {
|
||||||
)
|
)
|
||||||
presenter.checkNow()
|
presenter.checkNow()
|
||||||
|
|
||||||
verify(view).setLoading()
|
verify(view, never()).setLoading()
|
||||||
verify(view).displayModel(newModel)
|
verify(view).displayModel(newModel)
|
||||||
verify(checkStatusManager).scheduleCheck(
|
verify(checkStatusManager).scheduleCheck(
|
||||||
site = newModel,
|
site = newModel,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import android.content.ContentValues
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import com.afollestad.nocknock.data.ServerStatus.OK
|
import com.afollestad.nocknock.data.ServerStatus.OK
|
||||||
import com.afollestad.nocknock.utilities.ext.timeString
|
import com.afollestad.nocknock.utilities.ext.timeString
|
||||||
import java.io.Serializable
|
import com.afollestad.nocknock.utilities.providers.IdProvider
|
||||||
import java.lang.System.currentTimeMillis
|
import java.lang.System.currentTimeMillis
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ data class ServerModel(
|
||||||
val validationMode: ValidationMode,
|
val validationMode: ValidationMode,
|
||||||
val validationContent: String? = null,
|
val validationContent: String? = null,
|
||||||
val disabled: Boolean = false
|
val disabled: Boolean = false
|
||||||
) : Serializable {
|
) : IdProvider {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TABLE_NAME = "server_models"
|
const val TABLE_NAME = "server_models"
|
||||||
|
@ -63,6 +63,8 @@ data class ServerModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun id() = id
|
||||||
|
|
||||||
fun intervalText(): String {
|
fun intervalText(): String {
|
||||||
val now = currentTimeMillis()
|
val now = currentTimeMillis()
|
||||||
val nextCheck = max(lastCheck, 0) + checkInterval
|
val nextCheck = max(lastCheck, 0) + checkInterval
|
||||||
|
|
|
@ -7,10 +7,12 @@ package com.afollestad.nocknock.engine.db
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
|
import android.util.Log
|
||||||
import com.afollestad.nocknock.data.ServerModel
|
import com.afollestad.nocknock.data.ServerModel
|
||||||
import com.afollestad.nocknock.data.ServerModel.Companion.COLUMN_ID
|
import com.afollestad.nocknock.data.ServerModel.Companion.COLUMN_ID
|
||||||
import com.afollestad.nocknock.data.ServerModel.Companion.DEFAULT_SORT_ORDER
|
import com.afollestad.nocknock.data.ServerModel.Companion.DEFAULT_SORT_ORDER
|
||||||
import com.afollestad.nocknock.data.ServerModel.Companion.TABLE_NAME
|
import com.afollestad.nocknock.data.ServerModel.Companion.TABLE_NAME
|
||||||
|
import com.afollestad.nocknock.engine.BuildConfig
|
||||||
import com.afollestad.nocknock.utilities.ext.diffFrom
|
import com.afollestad.nocknock.utilities.ext.diffFrom
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -31,9 +33,22 @@ interface ServerModelStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @author Aidan Follestad (@afollestad) */
|
/** @author Aidan Follestad (@afollestad) */
|
||||||
class RealServerModelStore @Inject constructor(
|
class RealServerModelStore @Inject constructor(app: Application) : ServerModelStore {
|
||||||
app: Application
|
|
||||||
) : ServerModelStore {
|
companion object {
|
||||||
|
private fun log(
|
||||||
|
message: String,
|
||||||
|
warning: Boolean = false
|
||||||
|
) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
if (warning) {
|
||||||
|
Log.w("ServerModelStore", message)
|
||||||
|
} else {
|
||||||
|
Log.d("ServerModelStore", message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val dbHelper = ServerModelDbHelper(app)
|
private val dbHelper = ServerModelDbHelper(app)
|
||||||
|
|
||||||
|
@ -84,6 +99,9 @@ class RealServerModelStore @Inject constructor(
|
||||||
val newId = writer.insert(TABLE_NAME, null, model.toContentValues())
|
val newId = writer.insert(TABLE_NAME, null, model.toContentValues())
|
||||||
|
|
||||||
return model.copy(id = newId.toInt())
|
return model.copy(id = newId.toInt())
|
||||||
|
.apply {
|
||||||
|
log("Inserted new site model: $this")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun update(model: ServerModel): Int {
|
override suspend fun update(model: ServerModel): Int {
|
||||||
|
@ -96,9 +114,15 @@ class RealServerModelStore @Inject constructor(
|
||||||
val newValues = model.toContentValues()
|
val newValues = model.toContentValues()
|
||||||
val valuesDiff = oldValues.diffFrom(newValues)
|
val valuesDiff = oldValues.diffFrom(newValues)
|
||||||
|
|
||||||
|
if (valuesDiff.size() == 0) {
|
||||||
|
log("Nothing has changed - nothing to update!", warning = true)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
val selection = "$COLUMN_ID = ?"
|
val selection = "$COLUMN_ID = ?"
|
||||||
val selectionArgs = arrayOf("${model.id}")
|
val selectionArgs = arrayOf("${model.id}")
|
||||||
|
|
||||||
|
log("Updated model: $model")
|
||||||
return writer.update(TABLE_NAME, valuesDiff, selection, selectionArgs)
|
return writer.update(TABLE_NAME, valuesDiff, selection, selectionArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,10 +133,13 @@ class RealServerModelStore @Inject constructor(
|
||||||
|
|
||||||
val selection = "$COLUMN_ID = ?"
|
val selection = "$COLUMN_ID = ?"
|
||||||
val selectionArgs = arrayOf("$id")
|
val selectionArgs = arrayOf("$id")
|
||||||
|
|
||||||
|
log("Deleted model: $id")
|
||||||
return dbHelper.writableDatabase.delete(TABLE_NAME, selection, selectionArgs)
|
return dbHelper.writableDatabase.delete(TABLE_NAME, selection, selectionArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteAll(): Int {
|
override suspend fun deleteAll(): Int {
|
||||||
|
log("Deleted all models")
|
||||||
return dbHelper.writableDatabase.delete(TABLE_NAME, null, null)
|
return dbHelper.writableDatabase.delete(TABLE_NAME, null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ class CheckStatusJob : JobService() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
resultModel
|
updateStatus(site = resultModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JAVASCRIPT -> {
|
JAVASCRIPT -> {
|
||||||
|
@ -108,6 +108,7 @@ class CheckStatusJob : JobService() {
|
||||||
}
|
}
|
||||||
STATUS_CODE -> {
|
STATUS_CODE -> {
|
||||||
// We already know the status code is successful because we are in this else branch
|
// We already know the status code is successful because we are in this else branch
|
||||||
|
log("Using STATUS_CODE validation, which has passed!")
|
||||||
updateStatus(
|
updateStatus(
|
||||||
resultModel.copy(
|
resultModel.copy(
|
||||||
status = OK,
|
status = OK,
|
||||||
|
|
|
@ -18,10 +18,10 @@ enum class Channel(
|
||||||
val description: Int,
|
val description: Int,
|
||||||
val importance: Int
|
val importance: Int
|
||||||
) {
|
) {
|
||||||
Statuses(
|
CheckFailures(
|
||||||
id = "statuses",
|
id = "check_failures",
|
||||||
title = R.string.channel_server_status_title,
|
title = R.string.channel_server_check_failures_title,
|
||||||
description = R.string.channel_server_status_description,
|
description = R.string.channel_server_check_failures_description,
|
||||||
importance = IMPORTANCE_DEFAULT
|
importance = IMPORTANCE_DEFAULT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,16 +8,14 @@ package com.afollestad.nocknock.notifications
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.app.PendingIntent.FLAG_CANCEL_CURRENT
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
import android.os.Build.VERSION_CODES
|
import android.os.Build.VERSION_CODES
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat.DEFAULT_VIBRATE
|
import androidx.core.app.NotificationCompat.DEFAULT_VIBRATE
|
||||||
import com.afollestad.nocknock.data.ServerModel
|
import com.afollestad.nocknock.data.ServerModel
|
||||||
import com.afollestad.nocknock.notifications.Channel.Statuses
|
import com.afollestad.nocknock.notifications.Channel.CheckFailures
|
||||||
import com.afollestad.nocknock.utilities.providers.BitmapProvider
|
import com.afollestad.nocknock.utilities.providers.BitmapProvider
|
||||||
|
import com.afollestad.nocknock.utilities.providers.IntentProvider
|
||||||
|
import com.afollestad.nocknock.utilities.providers.RealIntentProvider.Companion.BASE_NOTIFICATION_REQUEST_CODE
|
||||||
import com.afollestad.nocknock.utilities.providers.StringProvider
|
import com.afollestad.nocknock.utilities.providers.StringProvider
|
||||||
import com.afollestad.nocknock.utilities.qualifiers.AppIconRes
|
import com.afollestad.nocknock.utilities.qualifiers.AppIconRes
|
||||||
import com.afollestad.nocknock.utilities.util.hasOreo
|
import com.afollestad.nocknock.utilities.util.hasOreo
|
||||||
|
@ -43,13 +41,11 @@ class RealNockNotificationManager @Inject constructor(
|
||||||
@AppIconRes private val appIconRes: Int,
|
@AppIconRes private val appIconRes: Int,
|
||||||
private val stockManager: NotificationManager,
|
private val stockManager: NotificationManager,
|
||||||
private val bitmapProvider: BitmapProvider,
|
private val bitmapProvider: BitmapProvider,
|
||||||
private val stringProvider: StringProvider
|
private val stringProvider: StringProvider,
|
||||||
|
private val intentProvider: IntentProvider
|
||||||
) : NockNotificationManager {
|
) : NockNotificationManager {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val BASE_REQUEST_CODE = 44
|
|
||||||
|
|
||||||
const val KEY_MODEL = "model"
|
|
||||||
|
|
||||||
private fun log(message: String) {
|
private fun log(message: String) {
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Log.d("NockNotificationManager", message)
|
Log.d("NockNotificationManager", message)
|
||||||
|
@ -64,10 +60,8 @@ class RealNockNotificationManager @Inject constructor(
|
||||||
log("Is app open? $open")
|
log("Is app open? $open")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createChannels() {
|
override fun createChannels() =
|
||||||
Channel.values()
|
Channel.values().forEach(this::createChannel)
|
||||||
.forEach(this::createChannel)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun postStatusNotification(model: ServerModel) {
|
override fun postStatusNotification(model: ServerModel) {
|
||||||
if (isAppOpen) {
|
if (isAppOpen) {
|
||||||
|
@ -77,23 +71,12 @@ class RealNockNotificationManager @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Posting status notification for site ${model.id}...")
|
log("Posting status notification for site ${model.id}...")
|
||||||
val viewSiteActivityCls =
|
val intent = intentProvider.getPendingIntentForViewSite(model)
|
||||||
Class.forName("com.afollestad.nocknock.ui.viewsite.ViewSiteActivity")
|
|
||||||
val openIntent = Intent(app, viewSiteActivityCls).apply {
|
|
||||||
putExtra(KEY_MODEL, model)
|
|
||||||
addFlags(FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
}
|
|
||||||
val openPendingIntent = PendingIntent.getBroadcast(
|
|
||||||
app,
|
|
||||||
BASE_REQUEST_CODE + model.id,
|
|
||||||
openIntent,
|
|
||||||
FLAG_CANCEL_CURRENT
|
|
||||||
)
|
|
||||||
|
|
||||||
val newNotification = notification(app, Statuses) {
|
val newNotification = notification(app, CheckFailures) {
|
||||||
setContentTitle(model.name)
|
setContentTitle(model.name)
|
||||||
setContentText(stringProvider.get(R.string.something_wrong))
|
setContentText(stringProvider.get(R.string.something_wrong))
|
||||||
setContentIntent(openPendingIntent)
|
setContentIntent(intent)
|
||||||
setSmallIcon(R.drawable.ic_notification)
|
setSmallIcon(R.drawable.ic_notification)
|
||||||
setLargeIcon(bitmapProvider.get(appIconRes))
|
setLargeIcon(bitmapProvider.get(appIconRes))
|
||||||
setAutoCancel(true)
|
setAutoCancel(true)
|
||||||
|
@ -109,9 +92,7 @@ class RealNockNotificationManager @Inject constructor(
|
||||||
log("Cancelled status notification for site ${model.id}.")
|
log("Cancelled status notification for site ${model.id}.")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancelStatusNotifications() {
|
override fun cancelStatusNotifications() = stockManager.cancelAll()
|
||||||
stockManager.cancelAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(VERSION_CODES.O)
|
@TargetApi(VERSION_CODES.O)
|
||||||
private fun createChannel(channel: Channel) {
|
private fun createChannel(channel: Channel) {
|
||||||
|
@ -119,10 +100,11 @@ class RealNockNotificationManager @Inject constructor(
|
||||||
log("Not running Android O, channels won't be created.")
|
log("Not running Android O, channels won't be created.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val notificationChannel = channel.toNotificationChannel(app)
|
val notificationChannel = channel.toNotificationChannel(app)
|
||||||
stockManager.createNotificationChannel(notificationChannel)
|
stockManager.createNotificationChannel(notificationChannel)
|
||||||
log("Created notification channel ${channel.id}")
|
log("Created notification channel ${channel.id}")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ServerModel.notificationId() = BASE_REQUEST_CODE + this.id
|
private fun ServerModel.notificationId() = BASE_NOTIFICATION_REQUEST_CODE + this.id
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<string name="channel_server_status_title">Server Statuses</string>
|
<string name="channel_server_check_failures_title">Server Check Failures</string>
|
||||||
<string name="channel_server_status_description">
|
<string name="channel_server_check_failures_description">
|
||||||
Notifications for server status changes, whether it\'s successful statuses or error statuses.
|
Notifications for Nock Nock status checks failing for your sites. Something has gone
|
||||||
|
wrong if you see one of these.
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
<string name="something_wrong">Something\'s wrong! Tap for details.</string>
|
<string name="something_wrong">Something\'s wrong! Tap for details.</string>
|
||||||
|
|
|
@ -6,7 +6,9 @@
|
||||||
package com.afollestad.nocknock.utilities
|
package com.afollestad.nocknock.utilities
|
||||||
|
|
||||||
import com.afollestad.nocknock.utilities.providers.BitmapProvider
|
import com.afollestad.nocknock.utilities.providers.BitmapProvider
|
||||||
|
import com.afollestad.nocknock.utilities.providers.IntentProvider
|
||||||
import com.afollestad.nocknock.utilities.providers.RealBitmapProvider
|
import com.afollestad.nocknock.utilities.providers.RealBitmapProvider
|
||||||
|
import com.afollestad.nocknock.utilities.providers.RealIntentProvider
|
||||||
import com.afollestad.nocknock.utilities.providers.RealStringProvider
|
import com.afollestad.nocknock.utilities.providers.RealStringProvider
|
||||||
import com.afollestad.nocknock.utilities.providers.StringProvider
|
import com.afollestad.nocknock.utilities.providers.StringProvider
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
|
@ -28,4 +30,10 @@ abstract class UtilitiesModule {
|
||||||
abstract fun provideStringProvider(
|
abstract fun provideStringProvider(
|
||||||
stringProvider: RealStringProvider
|
stringProvider: RealStringProvider
|
||||||
): StringProvider
|
): StringProvider
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun provideIntentProvider(
|
||||||
|
intentProvider: RealIntentProvider
|
||||||
|
): IntentProvider
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ package com.afollestad.nocknock.utilities.ext
|
||||||
|
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
fun Animator.onEnd(cb: () -> Unit) {
|
fun Animator.onEnd(cb: () -> Unit) {
|
||||||
addListener(object : AnimatorListenerAdapter() {
|
addListener(object : AnimatorListenerAdapter() {
|
||||||
|
@ -16,3 +17,34 @@ fun Animator.onEnd(cb: () -> Unit) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun View.animateRotation(
|
||||||
|
loop: Boolean = true,
|
||||||
|
firstPass: Boolean = true,
|
||||||
|
durationPerRotation: Long = 1000,
|
||||||
|
degreesPerRotation: Float = 360f
|
||||||
|
) {
|
||||||
|
if (firstPass) {
|
||||||
|
animate().cancel()
|
||||||
|
}
|
||||||
|
animate()
|
||||||
|
.rotationBy(degreesPerRotation)
|
||||||
|
.setDuration(durationPerRotation)
|
||||||
|
.setListener(object : AnimatorListenerAdapter() {
|
||||||
|
|
||||||
|
var isCancelled = false
|
||||||
|
|
||||||
|
override fun onAnimationCancel(animation: Animator?) {
|
||||||
|
super.onAnimationCancel(animation)
|
||||||
|
isCancelled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
|
super.onAnimationEnd(animation)
|
||||||
|
if (loop && !isCancelled) {
|
||||||
|
animateRotation(loop = true, firstPass = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Licensed under Apache-2.0
|
||||||
|
*
|
||||||
|
* Designed and developed by Aidan Follestad (@afollestad)
|
||||||
|
*/
|
||||||
|
package com.afollestad.nocknock.utilities.providers
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.PendingIntent.FLAG_CANCEL_CURRENT
|
||||||
|
import android.content.Intent
|
||||||
|
import com.afollestad.nocknock.utilities.qualifiers.MainActivityClass
|
||||||
|
import java.io.Serializable
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/** @author Aidan Follestad (@afollestad) */
|
||||||
|
interface IdProvider : Serializable {
|
||||||
|
|
||||||
|
fun id(): Int
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @author Aidan Follestad (@afollestad) */
|
||||||
|
interface IntentProvider {
|
||||||
|
|
||||||
|
fun getPendingIntentForViewSite(
|
||||||
|
model: IdProvider
|
||||||
|
): PendingIntent
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @author Aidan Follestad (@afollestad) */
|
||||||
|
class RealIntentProvider @Inject constructor(
|
||||||
|
private val app: Application,
|
||||||
|
@MainActivityClass private val mainActivity: Class<*>
|
||||||
|
) : IntentProvider {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val BASE_NOTIFICATION_REQUEST_CODE = 40
|
||||||
|
const val KEY_VIEW_NOTIFICATION_MODEL = "model"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPendingIntentForViewSite(model: IdProvider): PendingIntent {
|
||||||
|
val openIntent = getIntentForViewSite(model)
|
||||||
|
return PendingIntent.getActivity(
|
||||||
|
app,
|
||||||
|
BASE_NOTIFICATION_REQUEST_CODE + model.id(),
|
||||||
|
openIntent,
|
||||||
|
FLAG_CANCEL_CURRENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getIntentForViewSite(model: IdProvider) =
|
||||||
|
Intent(app, mainActivity).apply {
|
||||||
|
putExtra(KEY_VIEW_NOTIFICATION_MODEL, model)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* Licensed under Apache-2.0
|
||||||
|
*
|
||||||
|
* Designed and developed by Aidan Follestad (@afollestad)
|
||||||
|
*/
|
||||||
|
package com.afollestad.nocknock.utilities.qualifiers
|
||||||
|
|
||||||
|
import javax.inject.Qualifier
|
||||||
|
import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||||
|
|
||||||
|
/** @author Aidan Follestad (@afollestad) */
|
||||||
|
@Qualifier
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
annotation class MainActivityClass
|
|
@ -30,7 +30,7 @@
|
||||||
android:background="@null"
|
android:background="@null"
|
||||||
android:fontFamily="@font/fira_mono"
|
android:fontFamily="@font/fira_mono"
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:inputType="textMultiLine"
|
android:inputType="textMultiLine|textNoSuggestions"
|
||||||
android:lineSpacingMultiplier="1.6"
|
android:lineSpacingMultiplier="1.6"
|
||||||
android:paddingBottom="@dimen/content_inset_less"
|
android:paddingBottom="@dimen/content_inset_less"
|
||||||
android:paddingEnd="@dimen/content_inset_more"
|
android:paddingEnd="@dimen/content_inset_more"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue