WIP use custom fonts, cleanup layouts, etc.

This commit is contained in:
Aidan Follestad 2018-11-30 18:28:09 -08:00
commit ef73245831
32 changed files with 336 additions and 270 deletions

View file

@ -52,6 +52,9 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts"/>
</application> </application>

View file

@ -20,9 +20,11 @@ import androidx.appcompat.app.AppCompatActivity
import com.afollestad.nocknock.R import com.afollestad.nocknock.R
import com.afollestad.nocknock.data.ServerModel import com.afollestad.nocknock.data.ServerModel
import com.afollestad.nocknock.data.ServerStatus.WAITING import com.afollestad.nocknock.data.ServerStatus.WAITING
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
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.engine.db.ServerModelStore import com.afollestad.nocknock.engine.db.ServerModelStore
import com.afollestad.nocknock.engine.statuscheck.CheckStatusManager import com.afollestad.nocknock.engine.statuscheck.CheckStatusManager
import com.afollestad.nocknock.utilities.ext.injector import com.afollestad.nocknock.utilities.ext.injector
@ -56,6 +58,7 @@ import kotlinx.coroutines.launch
import java.lang.System.currentTimeMillis import java.lang.System.currentTimeMillis
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.max import kotlin.math.max
import kotlin.properties.Delegates.notNull
private const val KEY_FAB_X = "fab_x" private const val KEY_FAB_X = "fab_x"
private const val KEY_FAB_Y = "fab_y" private const val KEY_FAB_Y = "fab_y"
@ -76,11 +79,19 @@ fun MainActivity.intentToAdd(
/** @author Aidan Follestad (afollestad) */ /** @author Aidan Follestad (afollestad) */
class AddSiteActivity : AppCompatActivity(), View.OnClickListener { class AddSiteActivity : AppCompatActivity(), View.OnClickListener {
companion object {
private const val REVEAL_DURATION = 300L
}
private var isClosing: Boolean = false private var isClosing: Boolean = false
@Inject lateinit var serverModelStore: ServerModelStore @Inject lateinit var serverModelStore: ServerModelStore
@Inject lateinit var checkStatusManager: CheckStatusManager @Inject lateinit var checkStatusManager: CheckStatusManager
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)
@ -91,7 +102,19 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener {
if (savedInstanceState == null) { if (savedInstanceState == null) {
rootView.conceal() rootView.conceal()
rootView.onLayout { circularRevealActivity() } rootView.onLayout {
val fabSize = intent.getIntExtra(KEY_FAB_SIZE, 0)
val fabX = intent.getFloatExtra(KEY_FAB_X, 0f)
.toInt()
val fabY = intent.getFloatExtra(KEY_FAB_Y, 0f)
.toInt()
revealCx = fabX + fabSize / 2
revealCy = (fabY + toolbar.measuredHeight + fabSize / 2)
revealRadius = max(revealCx, revealCy).toFloat()
circularRevealActivity()
}
} }
inputUrl.setOnFocusChangeListener { _, hasFocus -> inputUrl.setOnFocusChangeListener { _, hasFocus ->
@ -141,27 +164,23 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener {
doneBtn.setOnClickListener(this) doneBtn.setOnClickListener(this)
} }
private fun closeActivityWithReveal() { private fun circularRevealActivity() {
if (isClosing) { val circularReveal =
return createCircularReveal(rootView, revealCx, revealCy, 0f, revealRadius)
.apply {
duration = REVEAL_DURATION
interpolator = DecelerateInterpolator()
}
rootView.show()
circularReveal.start()
} }
private fun closeActivityWithReveal() {
if (isClosing) return
isClosing = true isClosing = true
val fabSize = intent.getIntExtra(KEY_FAB_SIZE, toolbar!!.measuredHeight) createCircularReveal(rootView, revealCx, revealCy, revealRadius, 0f)
val defaultCx = rootView.measuredWidth / 2f
val cx =
intent.getFloatExtra(KEY_FAB_X, defaultCx).toInt() + fabSize / 2
val defaultCy = rootView.measuredHeight / 2f
val cy = (intent.getFloatExtra(KEY_FAB_Y, defaultCy).toInt() +
toolbar!!.measuredHeight +
fabSize / 2)
val initialRadius = max(cx, cy).toFloat()
createCircularReveal(rootView, cx, cy, initialRadius, 0f)
.apply { .apply {
duration = 300 duration = REVEAL_DURATION
interpolator = AccelerateInterpolator() interpolator = AccelerateInterpolator()
onEnd { onEnd {
rootView.conceal() rootView.conceal()
@ -172,20 +191,6 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener {
} }
} }
private fun circularRevealActivity() {
val cx = rootView.measuredWidth / 2
val cy = rootView.measuredHeight / 2
val finalRadius = Math.max(cx, cy)
.toFloat()
val circularReveal = createCircularReveal(rootView, cx, cy, 0f, finalRadius)
.apply {
duration = 300
interpolator = DecelerateInterpolator()
}
rootView.show()
circularReveal.start()
}
// Done button // Done button
override fun onClick(view: View) { override fun onClick(view: View) {
isClosing = true isClosing = true
@ -223,14 +228,14 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener {
} }
val selectedCheckInterval = checkIntervalLayout.getSelectedCheckInterval() val selectedCheckInterval = checkIntervalLayout.getSelectedCheckInterval()
val selectedValidationMode = getSelectedValidationMode() val selectedValidationMode =
val selectedValidationContent = getSelectedValidationContent() responseValidationMode.selectedItemPosition.indexToValidationMode()
newModel = newModel.copy( newModel = newModel.copy(
checkInterval = selectedCheckInterval, checkInterval = selectedCheckInterval,
lastCheck = currentTimeMillis() - selectedCheckInterval, lastCheck = currentTimeMillis() - selectedCheckInterval,
validationMode = selectedValidationMode, validationMode = selectedValidationMode,
validationContent = selectedValidationContent validationContent = selectedValidationMode.validationContent()
) )
rootView.scopeWhileAttached(Main) { rootView.scopeWhileAttached(Main) {
@ -250,25 +255,9 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener {
override fun onBackPressed() = closeActivityWithReveal() override fun onBackPressed() = closeActivityWithReveal()
private fun getSelectedValidationMode() = when (responseValidationMode.selectedItemPosition) { private fun ValidationMode.validationContent() = when (this) {
0 -> STATUS_CODE STATUS_CODE -> null
1 -> TERM_SEARCH TERM_SEARCH -> responseValidationSearchTerm.trimmedText()
2 -> JAVASCRIPT JAVASCRIPT -> scriptInputLayout.getCode()
else -> {
throw IllegalStateException(
"Unexpected validation mode index: ${responseValidationMode.selectedItemPosition}"
)
}
}
private fun getSelectedValidationContent() = when (responseValidationMode.selectedItemPosition) {
0 -> null
1 -> responseValidationSearchTerm.trimmedText()
2 -> scriptInputLayout.getCode()
else -> {
throw IllegalStateException(
"Unexpected validation mode index: ${responseValidationMode.selectedItemPosition}"
)
}
} }
} }

View file

@ -5,8 +5,6 @@
*/ */
package com.afollestad.nocknock.ui package com.afollestad.nocknock.ui
import android.animation.ObjectAnimator
import android.animation.ObjectAnimator.ofFloat
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.ActivityOptions.makeSceneTransitionAnimation import android.app.ActivityOptions.makeSceneTransitionAnimation
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
@ -15,12 +13,6 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.View.X
import android.view.View.Y
import android.view.animation.PathInterpolator
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
@ -40,17 +32,16 @@ import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.KEY_U
import com.afollestad.nocknock.engine.statuscheck.CheckStatusManager import com.afollestad.nocknock.engine.statuscheck.CheckStatusManager
import com.afollestad.nocknock.notifications.NockNotificationManager import com.afollestad.nocknock.notifications.NockNotificationManager
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.safeRegisterReceiver import com.afollestad.nocknock.utilities.ext.safeRegisterReceiver
import com.afollestad.nocknock.utilities.ext.safeUnregisterReceiver import com.afollestad.nocknock.utilities.ext.safeUnregisterReceiver
import com.afollestad.nocknock.utilities.ext.scopeWhileAttached import com.afollestad.nocknock.utilities.ext.scopeWhileAttached
import com.afollestad.nocknock.viewcomponents.ext.show 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.utilities.util.MathUtil.bezierCurve
import kotlinx.android.synthetic.main.activity_main.emptyText
import kotlinx.android.synthetic.main.activity_main.fab import kotlinx.android.synthetic.main.activity_main.fab
import kotlinx.android.synthetic.main.activity_main.list import kotlinx.android.synthetic.main.activity_main.list
import kotlinx.android.synthetic.main.activity_main.rootView import kotlinx.android.synthetic.main.activity_main.rootView
import kotlinx.android.synthetic.main.activity_main.toolbar
import kotlinx.android.synthetic.main.include_empty_view.emptyText
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.async import kotlinx.coroutines.async
@ -58,12 +49,11 @@ import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
/** @author Aidan Follestad (afollestad) */ /** @author Aidan Follestad (afollestad) */
class MainActivity : AppCompatActivity(), View.OnClickListener { class MainActivity : AppCompatActivity() {
companion object { companion object {
private const val ADD_SITE_RQ = 6969 private const val ADD_SITE_RQ = 6969
private const val VIEW_SITE_RQ = 6923 private const val VIEW_SITE_RQ = 6923
private const val REVEAL_DURATION = 250L
private fun log(message: String) { private fun log(message: String) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
@ -72,10 +62,6 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
} }
} }
private var fabAnimator: ObjectAnimator? = null
private var originalFabX: Float = 0.toFloat()
private var originalFabY: Float = 0.toFloat()
private val intentReceiver = object : BroadcastReceiver() { private val intentReceiver = object : BroadcastReceiver() {
override fun onReceive( override fun onReceive(
context: Context, context: Context,
@ -105,13 +91,26 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
injector().injectInto(this) injector().injectInto(this)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
toolbar.inflateMenu(R.menu.menu_main)
toolbar.setOnMenuItemClickListener { item ->
if (item.itemId == R.id.about) {
AboutDialog.show(this)
}
return@setOnMenuItemClickListener true
}
adapter = ServerAdapter(this::onSiteSelected) adapter = ServerAdapter(this::onSiteSelected)
list.layoutManager = LinearLayoutManager(this) list.layoutManager = LinearLayoutManager(this)
list.adapter = adapter list.adapter = adapter
list.addItemDecoration(DividerItemDecoration(this, VERTICAL)) list.addItemDecoration(DividerItemDecoration(this, VERTICAL))
fab.setOnClickListener(this) fab.setOnClickListener {
startActivityForResult(
intentToAdd(fab.x, fab.y, fab.measuredWidth),
ADD_SITE_RQ
)
}
notificationManager.createChannels() notificationManager.createChannels()
ensureCheckJobs() ensureCheckJobs()
@ -152,46 +151,6 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
} }
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.about) {
AboutDialog.show(this)
return true
}
return super.onOptionsItemSelected(item)
}
// FAB clicked
override fun onClick(view: View) {
originalFabX = fab.x
originalFabY = fab.y
fabAnimator?.cancel()
fabAnimator = ofFloat(view, X, Y, bezierCurve(fab, list))
.apply {
interpolator = PathInterpolator(.5f, .5f)
duration = REVEAL_DURATION
onEnd {
startActivityForResult(
intentToAdd(originalFabX, originalFabY, fab.measuredWidth),
ADD_SITE_RQ
)
fab.postDelayed(
{
fab.x = originalFabX
fab.y = originalFabY
},
REVEAL_DURATION * 2
)
}
start()
}
}
override fun onActivityResult( override fun onActivityResult(
requestCode: Int, requestCode: Int,
resultCode: Int, resultCode: Int,

View file

@ -24,12 +24,15 @@ import androidx.core.text.HtmlCompat
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.nocknock.BuildConfig import com.afollestad.nocknock.BuildConfig
import com.afollestad.nocknock.R import com.afollestad.nocknock.R
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.CHECKING
import com.afollestad.nocknock.data.ServerStatus.WAITING import com.afollestad.nocknock.data.ServerStatus.WAITING
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
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.textRes import com.afollestad.nocknock.data.textRes
import com.afollestad.nocknock.engine.db.ServerModelStore import com.afollestad.nocknock.engine.db.ServerModelStore
import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.ACTION_STATUS_UPDATE import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.ACTION_STATUS_UPDATE
@ -41,12 +44,17 @@ import com.afollestad.nocknock.utilities.ext.isHttpOrHttps
import com.afollestad.nocknock.utilities.ext.safeRegisterReceiver import com.afollestad.nocknock.utilities.ext.safeRegisterReceiver
import com.afollestad.nocknock.utilities.ext.safeUnregisterReceiver import com.afollestad.nocknock.utilities.ext.safeUnregisterReceiver
import com.afollestad.nocknock.utilities.ext.scopeWhileAttached import com.afollestad.nocknock.utilities.ext.scopeWhileAttached
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
import com.afollestad.nocknock.viewcomponents.ext.disable
import com.afollestad.nocknock.viewcomponents.ext.enable
import com.afollestad.nocknock.viewcomponents.ext.hide import com.afollestad.nocknock.viewcomponents.ext.hide
import com.afollestad.nocknock.viewcomponents.ext.onItemSelected import com.afollestad.nocknock.viewcomponents.ext.onItemSelected
import com.afollestad.nocknock.viewcomponents.ext.onScroll
import com.afollestad.nocknock.viewcomponents.ext.show 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_viewsite.checkIntervalLayout import kotlinx.android.synthetic.main.activity_viewsite.checkIntervalLayout
import kotlinx.android.synthetic.main.activity_viewsite.disableChecksButton
import kotlinx.android.synthetic.main.activity_viewsite.doneBtn import kotlinx.android.synthetic.main.activity_viewsite.doneBtn
import kotlinx.android.synthetic.main.activity_viewsite.iconStatus import kotlinx.android.synthetic.main.activity_viewsite.iconStatus
import kotlinx.android.synthetic.main.activity_viewsite.inputName import kotlinx.android.synthetic.main.activity_viewsite.inputName
@ -56,6 +64,7 @@ import kotlinx.android.synthetic.main.activity_viewsite.responseValidationMode
import kotlinx.android.synthetic.main.activity_viewsite.responseValidationSearchTerm import kotlinx.android.synthetic.main.activity_viewsite.responseValidationSearchTerm
import kotlinx.android.synthetic.main.activity_viewsite.rootView import kotlinx.android.synthetic.main.activity_viewsite.rootView
import kotlinx.android.synthetic.main.activity_viewsite.scriptInputLayout import kotlinx.android.synthetic.main.activity_viewsite.scriptInputLayout
import kotlinx.android.synthetic.main.activity_viewsite.scrollView
import kotlinx.android.synthetic.main.activity_viewsite.textLastCheckResult import kotlinx.android.synthetic.main.activity_viewsite.textLastCheckResult
import kotlinx.android.synthetic.main.activity_viewsite.textNextCheck import kotlinx.android.synthetic.main.activity_viewsite.textNextCheck
import kotlinx.android.synthetic.main.activity_viewsite.textUrlWarning import kotlinx.android.synthetic.main.activity_viewsite.textUrlWarning
@ -122,6 +131,14 @@ class ViewSiteActivity : AppCompatActivity(),
setOnMenuItemClickListener(this@ViewSiteActivity) setOnMenuItemClickListener(this@ViewSiteActivity)
} }
scrollView.onScroll {
toolbar.elevation = if (it > toolbar.height / 4) {
toolbar.dimenFloat(R.dimen.default_elevation)
} else {
0f
}
}
inputUrl.setOnFocusChangeListener { _, hasFocus -> inputUrl.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) { if (!hasFocus) {
val inputStr = inputUrl.text val inputStr = inputUrl.text
@ -184,7 +201,7 @@ class ViewSiteActivity : AppCompatActivity(),
inputName.setText(this.name) inputName.setText(this.name)
inputUrl.setText(this.url) inputUrl.setText(this.url)
if (this.lastCheck == 0L) { if (this.lastCheck == LAST_CHECK_NONE) {
textLastCheckResult.setText(R.string.none) textLastCheckResult.setText(R.string.none)
} else { } else {
val statusText = this.status.textRes() val statusText = this.status.textRes()
@ -195,16 +212,8 @@ class ViewSiteActivity : AppCompatActivity(),
} }
} }
if (this.checkInterval == 0L) { textNextCheck.text = (this.lastCheck + this.checkInterval).formatDate()
textNextCheck.setText(R.string.none_turned_off) checkIntervalLayout.set(this.checkInterval)
checkIntervalLayout.clear()
} else {
var lastCheck = this.lastCheck
if (lastCheck == 0L) {
lastCheck = currentTimeMillis()
}
textNextCheck.text = (lastCheck + this.checkInterval).formatDate()
}
responseValidationMode.setSelection(validationMode.value - 1) responseValidationMode.setSelection(validationMode.value - 1)
@ -267,14 +276,14 @@ class ViewSiteActivity : AppCompatActivity(),
} }
val selectedCheckInterval = checkIntervalLayout.getSelectedCheckInterval() val selectedCheckInterval = checkIntervalLayout.getSelectedCheckInterval()
val selectedValidationMode = getSelectedValidationMode() val selectedValidationMode =
val selectedValidationContent = getSelectedValidationContent() responseValidationMode.selectedItemPosition.indexToValidationMode()
currentModel = currentModel.copy( currentModel = currentModel.copy(
checkInterval = selectedCheckInterval, checkInterval = selectedCheckInterval,
lastCheck = currentTimeMillis() - selectedCheckInterval, lastCheck = currentTimeMillis() - selectedCheckInterval,
validationMode = selectedValidationMode, validationMode = selectedValidationMode,
validationContent = selectedValidationContent validationContent = selectedValidationMode.validationContent()
) )
return true return true
@ -307,6 +316,7 @@ class ViewSiteActivity : AppCompatActivity(),
R.id.refresh -> { R.id.refresh -> {
rootView.scopeWhileAttached(Main) { rootView.scopeWhileAttached(Main) {
launch(coroutineContext) { launch(coroutineContext) {
disableChecksButton.disable()
loadingProgress.setLoading() loadingProgress.setLoading()
updateModelFromInput(false) updateModelFromInput(false)
currentModel = currentModel.copy(status = WAITING) currentModel = currentModel.copy(status = WAITING)
@ -317,6 +327,7 @@ class ViewSiteActivity : AppCompatActivity(),
checkStatusManager.cancelCheck(currentModel) checkStatusManager.cancelCheck(currentModel)
checkStatusManager.scheduleCheck(currentModel, rightNow = true) checkStatusManager.scheduleCheck(currentModel, rightNow = true)
loadingProgress.setDone() loadingProgress.setDone()
disableChecksButton.enable()
} }
} }
return true return true
@ -363,25 +374,9 @@ class ViewSiteActivity : AppCompatActivity(),
item.isEnabled = currentModel.status != CHECKING && currentModel.status != WAITING item.isEnabled = currentModel.status != CHECKING && currentModel.status != WAITING
} }
private fun getSelectedValidationMode() = when (responseValidationMode.selectedItemPosition) { private fun ValidationMode.validationContent() = when (this) {
0 -> STATUS_CODE STATUS_CODE -> null
1 -> TERM_SEARCH TERM_SEARCH -> responseValidationSearchTerm.trimmedText()
2 -> JAVASCRIPT JAVASCRIPT -> scriptInputLayout.getCode()
else -> {
throw IllegalStateException(
"Unexpected validation mode index: ${responseValidationMode.selectedItemPosition}"
)
}
}
private fun getSelectedValidationContent() = when (responseValidationMode.selectedItemPosition) {
0 -> null
1 -> responseValidationSearchTerm.trimmedText()
2 -> scriptInputLayout.getCode()
else -> {
throw IllegalStateException(
"Unexpected validation mode index: ${responseValidationMode.selectedItemPosition}"
)
}
} }
} }

View file

@ -19,9 +19,10 @@
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:theme="@style/FlatToolbarTheme"
app:navigationIcon="@drawable/ic_action_close" app:navigationIcon="@drawable/ic_action_close"
app:title="@string/add_site" app:title="@string/add_site"
app:titleTextColor="?android:textColorPrimary" app:titleTextColor="#FFFFFF"
/> />
<ScrollView <ScrollView
@ -51,7 +52,7 @@
android:id="@+id/inputName" android:id="@+id/inputName"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato"
android:hint="@string/site_name" android:hint="@string/site_name"
android:inputType="textPersonName|textCapWords|textAutoCorrect" android:inputType="textPersonName|textCapWords|textAutoCorrect"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
@ -74,7 +75,7 @@
android:id="@+id/inputUrl" android:id="@+id/inputUrl"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato"
android:hint="@string/site_url" android:hint="@string/site_url"
android:inputType="textUri" android:inputType="textUri"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
@ -89,7 +90,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/list_text_spacing" android:layout_marginTop="@dimen/list_text_spacing"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato_light"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
android:textSize="@dimen/caption_font_size" android:textSize="@dimen/caption_font_size"
android:visibility="gone" android:visibility="gone"
@ -106,11 +107,11 @@
/> />
<TextView <TextView
android:id="@+id/responseValidation" android:id="@+id/responseValidationLabel"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset" android:layout_marginTop="@dimen/content_inset"
android:fontFamily="sans-serif" android:fontFamily="@font/lato"
android:text="@string/response_validation_mode" android:text="@string/response_validation_mode"
android:textColor="?colorAccent" android:textColor="?colorAccent"
android:textSize="@dimen/caption_font_size" android:textSize="@dimen/caption_font_size"
@ -130,11 +131,11 @@
android:layout_marginLeft="-4dp" android:layout_marginLeft="-4dp"
android:layout_marginRight="-4dp" android:layout_marginRight="-4dp"
android:layout_marginTop="-4dp" android:layout_marginTop="-4dp"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato_light"
android:hint="@string/search_term" android:hint="@string/search_term"
android:textSize="@dimen/body_font_size" android:textSize="@dimen/body_font_size"
android:visibility="gone" android:visibility="gone"
tools:ignore="Autofill" tools:ignore="Autofill,TextFields"
/> />
<com.afollestad.nocknock.viewcomponents.JavaScriptInputLayout <com.afollestad.nocknock.viewcomponents.JavaScriptInputLayout
@ -151,7 +152,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset_half" android:layout_marginBottom="@dimen/content_inset_half"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato_light"
android:lineSpacingMultiplier="1.2" android:lineSpacingMultiplier="1.2"
android:text="@string/validation_mode_status_desc" android:text="@string/validation_mode_status_desc"
android:textSize="@dimen/body_font_size" android:textSize="@dimen/body_font_size"
@ -161,7 +162,7 @@
android:id="@+id/doneBtn" android:id="@+id/doneBtn"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset" android:layout_marginTop="@dimen/content_inset_double"
android:text="@string/done" android:text="@string/done"
style="@style/AccentButton" style="@style/AccentButton"
/> />

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootView" android:id="@+id/rootView"
@ -8,6 +9,20 @@
tools:context=".ui.MainActivity" tools:context=".ui.MainActivity"
> >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/MainToolbarTheme"
style="@style/MainToolbarStyle"
/>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/list" android:id="@+id/list"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -15,31 +30,22 @@
android:scrollbars="vertical" android:scrollbars="vertical"
/> />
<TextView </LinearLayout>
android:id="@+id/emptyText"
android:layout_width="match_parent" <include layout="@layout/include_empty_view"/>
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginBottom="@dimen/content_inset"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:text="@string/no_sites_added"
android:textSize="@dimen/empty_text_size"
android:textStyle="italic"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab" android:id="@+id/fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end|bottom" android:layout_gravity="end|bottom"
android:layout_margin="@dimen/content_inset"
android:src="@drawable/ic_add" android:src="@drawable/ic_add"
app:backgroundTint="?colorAccent" app:backgroundTint="?colorAccent"
app:elevation="@dimen/fab_elevation" app:elevation="@dimen/fab_elevation"
app:fabSize="normal" app:fabSize="normal"
app:pressedTranslationZ="@dimen/fab_elevation_pressed" app:pressedTranslationZ="@dimen/fab_elevation_pressed"
app:rippleColor="#40ffffff" app:rippleColor="#40ffffff"
app:useCompatPadding="true"
/> />
</FrameLayout> </FrameLayout>

View file

@ -15,16 +15,20 @@
android:orientation="vertical" android:orientation="vertical"
> >
<!-- Background is applied again here so programmatic elevation works -->
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?colorPrimary"
android:theme="@style/FlatToolbarTheme"
app:navigationIcon="@drawable/ic_action_close" app:navigationIcon="@drawable/ic_action_close"
app:title="@string/view_site" app:title="@string/view_site"
app:titleTextColor="?android:textColorPrimary" app:titleTextColor="?android:textColorPrimary"
/> />
<ScrollView <ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
> >
@ -66,7 +70,7 @@
android:id="@+id/inputName" android:id="@+id/inputName"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato"
android:hint="@string/site_name" android:hint="@string/site_name"
android:inputType="textPersonName|textCapWords|textAutoCorrect" android:inputType="textPersonName|textCapWords|textAutoCorrect"
android:singleLine="true" android:singleLine="true"
@ -74,14 +78,14 @@
android:textColorHint="?android:textColorSecondary" android:textColorHint="?android:textColorSecondary"
android:textSize="@dimen/body_font_size" android:textSize="@dimen/body_font_size"
android:transitionName="site_name" android:transitionName="site_name"
tools:ignore="UnusedAttribute" tools:ignore="Autofill,UnusedAttribute"
/> />
<EditText <EditText
android:id="@+id/inputUrl" android:id="@+id/inputUrl"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato"
android:hint="@string/site_url" android:hint="@string/site_url"
android:inputType="textUri" android:inputType="textUri"
android:singleLine="true" android:singleLine="true"
@ -89,7 +93,7 @@
android:textColorHint="?android:textColorSecondary" android:textColorHint="?android:textColorSecondary"
android:textSize="@dimen/body_font_size" android:textSize="@dimen/body_font_size"
android:transitionName="site_url" android:transitionName="site_url"
tools:ignore="UnusedAttribute" tools:ignore="Autofill,UnusedAttribute"
/> />
<TextView <TextView
@ -99,7 +103,7 @@
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginTop="@dimen/list_text_spacing" android:layout_marginTop="@dimen/list_text_spacing"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato_light"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
android:textSize="@dimen/caption_font_size" android:textSize="@dimen/caption_font_size"
android:visibility="gone" android:visibility="gone"
@ -132,11 +136,11 @@
/> />
<TextView <TextView
android:id="@+id/responseValidation" android:id="@+id/responseValidationLabel"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset" android:layout_marginTop="@dimen/content_inset"
android:fontFamily="sans-serif" android:fontFamily="@font/lato"
android:text="@string/response_validation_mode" android:text="@string/response_validation_mode"
android:textColor="?colorAccent" android:textColor="?colorAccent"
android:textSize="@dimen/caption_font_size" android:textSize="@dimen/caption_font_size"
@ -156,7 +160,7 @@
android:layout_marginLeft="-4dp" android:layout_marginLeft="-4dp"
android:layout_marginRight="-4dp" android:layout_marginRight="-4dp"
android:layout_marginTop="-4dp" android:layout_marginTop="-4dp"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato_light"
android:hint="@string/search_term" android:hint="@string/search_term"
android:textSize="@dimen/body_font_size" android:textSize="@dimen/body_font_size"
android:visibility="gone" android:visibility="gone"
@ -177,7 +181,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset_half" android:layout_marginBottom="@dimen/content_inset_half"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato"
android:lineSpacingMultiplier="1.2" android:lineSpacingMultiplier="1.2"
android:text="@string/validation_mode_status_desc" android:text="@string/validation_mode_status_desc"
android:textSize="@dimen/body_font_size" android:textSize="@dimen/body_font_size"
@ -199,7 +203,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/list_text_spacing" android:layout_marginTop="@dimen/list_text_spacing"
android:fontFamily="sans-serif" android:fontFamily="@font/lato"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_text_size" android:textSize="@dimen/medium_text_size"
tools:text="Everything checks out!" tools:text="Everything checks out!"
@ -219,7 +223,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/list_text_spacing" android:layout_marginTop="@dimen/list_text_spacing"
android:fontFamily="sans-serif" android:fontFamily="@font/lato"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_text_size" android:textSize="@dimen/medium_text_size"
tools:text="In 2 hours" tools:text="In 2 hours"
@ -229,11 +233,20 @@
android:id="@+id/doneBtn" android:id="@+id/doneBtn"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset_more" android:layout_marginTop="@dimen/content_inset_double"
android:text="@string/save" android:text="@string/save_changes"
style="@style/AccentButton" style="@style/AccentButton"
/> />
<com.google.android.material.button.MaterialButton
android:id="@+id/disableChecksButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset_half"
android:text="@string/disable_automatic_checks"
style="@style/PrimaryDarkButton"
/>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/emptyText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginBottom="@dimen/content_inset"
android:fontFamily="@font/lato_light"
android:gravity="center"
android:text="@string/no_sites_added"
android:textSize="@dimen/empty_text_size"
android:textStyle="italic"
/>

View file

@ -43,7 +43,7 @@
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/content_inset_half" android:layout_marginEnd="@dimen/content_inset_half"
android:layout_toStartOf="@+id/textInterval" android:layout_toStartOf="@+id/textInterval"
android:fontFamily="sans-serif-medium" android:fontFamily="@font/lato"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
android:textSize="@dimen/title_font_size" android:textSize="@dimen/title_font_size"
@ -57,7 +57,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato_light"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:textSize="@dimen/caption_font_size" android:textSize="@dimen/caption_font_size"
@ -71,7 +71,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/list_text_spacing" android:layout_marginTop="@dimen/list_text_spacing"
android:fontFamily="sans-serif" android:fontFamily="@font/lato"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:textSize="@dimen/body_font_size" android:textSize="@dimen/body_font_size"
@ -84,7 +84,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/list_text_spacing" android:layout_marginTop="@dimen/list_text_spacing"
android:fontFamily="sans-serif" android:fontFamily="@font/lato"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:textSize="@dimen/body_font_size" android:textSize="@dimen/body_font_size"

View file

@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<string name="app_name" tools:ignore="Typos">Nock Nock (BETA)</string> <string name="app_name">Nock Nock</string>
<string name="no_sites_added">No sites added!</string> <string name="no_sites_added">No sites added!</string>
@ -29,7 +29,8 @@
<string name="remove_site">Remove Site</string> <string name="remove_site">Remove Site</string>
<string name="remove_site_prompt"><![CDATA[Remove <b>%1$s</b> from your sites?]]></string> <string name="remove_site_prompt"><![CDATA[Remove <b>%1$s</b> from your sites?]]></string>
<string name="remove">Remove</string> <string name="remove">Remove</string>
<string name="save">Save</string> <string name="save_changes">Save Changes</string>
<string name="disable_automatic_checks">Disable Automatic Checks</string>
<string name="view_site">View Site</string> <string name="view_site">View Site</string>
<string name="last_check_result">Last Check Result</string> <string name="last_check_result">Last Check Result</string>
<string name="next_check">Next Check</string> <string name="next_check">Next Check</string>
@ -39,13 +40,23 @@
<string name="refresh_status">Refresh Status</string> <string name="refresh_status">Refresh Status</string>
<string name="warning_http_url"> <string name="warning_http_url">
Warning: this app checks for server availability with HTTP requests. It\'s recommended that you use an HTTP URL. Warning: this app checks for server availability with HTTP requests. It\'s recommended that you
use an HTTP URL.
</string> </string>
<string name="response_validation_mode">Response Validation Mode</string> <string name="response_validation_mode">Response Validation Mode</string>
<string name="search_term">Search term…</string> <string name="search_term">Search term…</string>
<string name="validation_mode_status_desc">The HTTP status code is checked. If it\'s a successful status code, the site passes the check.</string> <string name="validation_mode_status_desc">
<string name="validation_mode_term_desc">The status code check is done first. If it\'s successful, the response body is checked. If it contains your search term, the site passes the check.</string> The HTTP status code is checked. If it\'s a successful status code, the site passes the check.
<string name="validation_mode_javascript_desc">The status code check is done first. If it\'s successful, the response body is passed to the JavaScript function above. If the function returns true, the site passes the check. Throw an exception to pass custom error messages to Nock Nock.</string> </string>
<string name="validation_mode_term_desc">
The status code check is done first. If it\'s successful, the response body is checked.
If it contains your search term, the site passes the check.
</string>
<string name="validation_mode_javascript_desc">
The status code check is done first. If it\'s successful, the response body is passed to the
JavaScript function above. If the function returns true, the site passes the check. Throw an
exception to pass custom error messages to Nock Nock.
</string>
</resources> </resources>

View file

@ -1,6 +1,6 @@
<resources> <resources>
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar"> <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
@ -27,9 +27,38 @@
<item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowBackground">@android:color/transparent</item>
</style> </style>
<style name="MainToolbarTheme" parent="@style/Theme.MaterialComponents">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:fontFamily">@font/lato_black</item>
</style>
<style name="MainToolbarStyle" parent="@style/Widget.MaterialComponents.Toolbar">
<item name="android:background">?colorPrimary</item>
<item name="android:elevation">@dimen/default_elevation</item>
<item name="title">@string/app_name</item>
<item name="titleTextColor">#FFFFFF</item>
<item name="popupTheme">@style/Theme.MaterialComponents.Light.DarkActionBar</item>
</style>
<style name="FlatToolbarTheme" parent="@style/Theme.MaterialComponents">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:fontFamily">@font/lato_black</item>
</style>
<style name="AccentButton" parent="Widget.MaterialComponents.Button"> <style name="AccentButton" parent="Widget.MaterialComponents.Button">
<item name="android:textColor">#fff</item> <item name="android:textColor">#fff</item>
<item name="android:colorButtonNormal">@color/colorAccent</item> <item name="backgroundTint">@color/colorAccent</item>
<item name="android:fontFamily">@font/lato</item>
</style>
<style name="PrimaryDarkButton" parent="Widget.MaterialComponents.Button">
<item name="android:textColor">#fff</item>
<item name="backgroundTint">@color/colorPrimaryDark</item>
<item name="android:fontFamily">@font/lato</item>
</style> </style>
</resources> </resources>

View file

@ -12,17 +12,21 @@ import com.afollestad.nocknock.utilities.ext.timeString
import java.io.Serializable import java.io.Serializable
import java.lang.System.currentTimeMillis import java.lang.System.currentTimeMillis
const val CHECK_INTERVAL_UNSET = -1L
const val LAST_CHECK_NONE = -1L
/** @author Aidan Follestad (afollestad)*/ /** @author Aidan Follestad (afollestad)*/
data class ServerModel( data class ServerModel(
var id: Int = 0, var id: Int = 0,
val name: String = "Unknown", val name: String,
val url: String = "Unknown", val url: String,
val status: ServerStatus = OK, val status: ServerStatus = OK,
val checkInterval: Long = 0, val checkInterval: Long = CHECK_INTERVAL_UNSET,
val lastCheck: Long = 0, val lastCheck: Long = LAST_CHECK_NONE,
val reason: String? = null, val reason: String? = null,
val validationMode: ValidationMode, val validationMode: ValidationMode,
val validationContent: String? = null val validationContent: String? = null,
val disabled: Boolean = false
) : Serializable { ) : Serializable {
companion object { companion object {
@ -36,8 +40,9 @@ data class ServerModel(
const val COLUMN_REASON = "reason" const val COLUMN_REASON = "reason"
const val COLUMN_VALIDATION_MODE = "validation_mode" const val COLUMN_VALIDATION_MODE = "validation_mode"
const val COLUMN_VALIDATION_CONTENT = "validation_content" const val COLUMN_VALIDATION_CONTENT = "validation_content"
const val COLUMN_DISABLED = "disabled"
const val DEFAULT_SORT_ORDER = "$COLUMN_NAME ASC" const val DEFAULT_SORT_ORDER = "$COLUMN_NAME ASC, $COLUMN_DISABLED DESC"
fun pull(cursor: Cursor): ServerModel { fun pull(cursor: Cursor): ServerModel {
return ServerModel( return ServerModel(
@ -51,7 +56,8 @@ data class ServerModel(
validationMode = cursor.getInt( validationMode = cursor.getInt(
cursor.getColumnIndex(COLUMN_VALIDATION_MODE) cursor.getColumnIndex(COLUMN_VALIDATION_MODE)
).toValidationMode(), ).toValidationMode(),
validationContent = cursor.getString(cursor.getColumnIndex(COLUMN_VALIDATION_CONTENT)) validationContent = cursor.getString(cursor.getColumnIndex(COLUMN_VALIDATION_CONTENT)),
disabled = cursor.getInt(cursor.getColumnIndex(COLUMN_DISABLED)) == 1
) )
} }
} }
@ -73,5 +79,6 @@ data class ServerModel(
put(COLUMN_REASON, reason) put(COLUMN_REASON, reason)
put(COLUMN_VALIDATION_MODE, validationMode.value) put(COLUMN_VALIDATION_MODE, validationMode.value)
put(COLUMN_VALIDATION_CONTENT, validationContent) put(COLUMN_VALIDATION_CONTENT, validationContent)
put(COLUMN_DISABLED, disabled)
} }
} }

View file

@ -19,7 +19,16 @@ enum class ValidationMode(val value: Int) {
JAVASCRIPT.value -> JAVASCRIPT JAVASCRIPT.value -> JAVASCRIPT
else -> throw IllegalArgumentException("Unknown validationMode: $value") else -> throw IllegalArgumentException("Unknown validationMode: $value")
} }
fun fromIndex(index: Int) = when (index) {
0 -> STATUS_CODE
1 -> TERM_SEARCH
2 -> JAVASCRIPT
else -> throw IllegalArgumentException("Index out of range: $index")
}
} }
} }
fun Int.toValidationMode() = ValidationMode.fromValue(this) fun Int.toValidationMode() = ValidationMode.fromValue(this)
fun Int.indexToValidationMode() = ValidationMode.fromIndex(this)

View file

@ -20,7 +20,8 @@ private const val SQL_CREATE_ENTRIES =
"${ServerModel.COLUMN_LAST_CHECK} INTEGER," + "${ServerModel.COLUMN_LAST_CHECK} INTEGER," +
"${ServerModel.COLUMN_REASON} TEXT," + "${ServerModel.COLUMN_REASON} TEXT," +
"${ServerModel.COLUMN_VALIDATION_MODE} INTEGER," + "${ServerModel.COLUMN_VALIDATION_MODE} INTEGER," +
"${ServerModel.COLUMN_VALIDATION_CONTENT} TEXT)" "${ServerModel.COLUMN_VALIDATION_CONTENT} TEXT," +
"${ServerModel.COLUMN_DISABLED} INTEGER)"
private const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS ${ServerModel.TABLE_NAME}" private const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS ${ServerModel.TABLE_NAME}"
@ -29,7 +30,7 @@ class ServerModelDbHelper(context: Context) : SQLiteOpenHelper(
context, DATABASE_NAME, null, DATABASE_VERSION context, DATABASE_NAME, null, DATABASE_VERSION
) { ) {
companion object { companion object {
const val DATABASE_VERSION = 1 const val DATABASE_VERSION = 2
const val DATABASE_NAME = "ServerModels.db" const val DATABASE_NAME = "ServerModels.db"
} }

View file

@ -10,6 +10,9 @@ import java.util.Date
import java.util.Locale import java.util.Locale
fun Long.formatDate(): String { fun Long.formatDate(): String {
if (this <= 0) {
return "(None)"
}
val df = SimpleDateFormat("MMMM dd, hh:mm:ss a", Locale.getDefault()) val df = SimpleDateFormat("MMMM dd, hh:mm:ss a", Locale.getDefault())
return df.format(Date(this)) return df.format(Date(this))
} }

View file

@ -1,44 +0,0 @@
/*
* Licensed under Apache-2.0
*
* Designed and developed by Aidan Follestad (@afollestad)
*/
package com.afollestad.nocknock.utilities.util
import android.graphics.Path
import android.view.View
/** @author Aidan Follestad (afollestad) */
object MathUtil {
fun bezierCurve(
targetView: View,
rootView: View
): Path {
val fabCenterX = (targetView.x + targetView.measuredWidth / 2).toInt()
val fabCenterY = (targetView.y + targetView.measuredHeight / 2).toInt()
val endCenterX = rootView.measuredWidth / 2 - targetView.measuredWidth / 2
val endCenterY = rootView.measuredHeight / 2 - targetView.measuredHeight / 2
val halfX = (fabCenterX - endCenterX) / 2
val halfY = (fabCenterY - endCenterY) / 2
var controlX = endCenterX + halfX
var controlY = endCenterY + halfY
controlY -= halfY
controlX += halfX
val path = Path()
path.moveTo(targetView.x, targetView.y)
path.quadTo(
controlX.toFloat(),
controlY.toFloat(),
endCenterX.toFloat(),
endCenterY.toFloat()
)
return path
}
}

View file

@ -74,11 +74,6 @@ class CheckIntervalLayout(
} }
} }
fun clear() {
input.setText("")
spinner.setSelection(0)
}
@CheckResult fun getSelectedCheckInterval(): Long { @CheckResult fun getSelectedCheckInterval(): Long {
val intervalInput = input.textAsLong() val intervalInput = input.textAsLong()
val spinnerPos = spinner.selectedItemPosition val spinnerPos = spinner.selectedItemPosition

View file

@ -27,6 +27,8 @@ class LoadingIndicatorFrame(
setBackgroundColor(ContextCompat.getColor(context, R.color.loading_indicator_frame_background)) setBackgroundColor(ContextCompat.getColor(context, R.color.loading_indicator_frame_background))
hide() // hide self by default hide() // hide self by default
inflate(context, R.layout.loading_indicator_frame, this) inflate(context, R.layout.loading_indicator_frame, this)
isClickable = true
isFocusable = true
} }
fun setLoading() { fun setLoading() {

View file

@ -0,0 +1,11 @@
/*
* Licensed under Apache-2.0
*
* Designed and developed by Aidan Follestad (@afollestad)
*/
package com.afollestad.nocknock.viewcomponents.ext
import android.widget.ScrollView
fun ScrollView.onScroll(cb: (y: Int) -> Unit) =
viewTreeObserver.addOnScrollChangedListener { cb(scrollY) }

View file

@ -24,6 +24,14 @@ fun View.hide() {
visibility = GONE visibility = GONE
} }
fun View.enable() {
isEnabled = true
}
fun View.disable() {
isEnabled = false
}
fun View.showOrHide(show: Boolean) = if (show) show() else hide() fun View.showOrHide(show: Boolean) = if (show) show() else hide()
fun View.onLayout(cb: () -> Unit) { fun View.onLayout(cb: () -> Unit) {

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="Fira Mono"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="Lato"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="name=Lato&amp;weight=900"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="name=Lato&amp;weight=300"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View file

@ -5,13 +5,14 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
tools:parentTag="android.widget.LinearLayout"
> >
<TextView <TextView
android:id="@+id/label" android:id="@+id/label"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="sans-serif" android:fontFamily="@font/lato"
android:text="@string/check_interval" android:text="@string/check_interval"
android:textColor="?colorAccent" android:textColor="?colorAccent"
android:textSize="@dimen/caption_font_size" android:textSize="@dimen/caption_font_size"
@ -32,7 +33,7 @@
android:layout_marginEnd="@dimen/content_inset_half" android:layout_marginEnd="@dimen/content_inset_half"
android:layout_marginStart="-4dp" android:layout_marginStart="-4dp"
android:layout_weight="1" android:layout_weight="1"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato_light"
android:hint="0" android:hint="0"
android:inputType="number" android:inputType="number"
android:maxLength="6" android:maxLength="6"

View file

@ -16,7 +16,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="serif-monospace" android:fontFamily="@font/fira_mono"
android:lineSpacingMultiplier="1.4" android:lineSpacingMultiplier="1.4"
android:singleLine="true" android:singleLine="true"
android:text="@string/function_declaration" android:text="@string/function_declaration"
@ -28,7 +28,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@null" android:background="@null"
android:fontFamily="serif-monospace" android:fontFamily="@font/fira_mono"
android:gravity="top" android:gravity="top"
android:inputType="textMultiLine" android:inputType="textMultiLine"
android:lineSpacingMultiplier="1.6" android:lineSpacingMultiplier="1.6"
@ -45,7 +45,7 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="serif-monospace" android:fontFamily="@font/fira_mono"
android:text="@string/function_end" android:text="@string/function_end"
android:textSize="@dimen/code_font_size" android:textSize="@dimen/code_font_size"
/> />

View file

@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/button_height" android:layout_height="@dimen/button_height"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato"
android:gravity="center_vertical|start" android:gravity="center_vertical|start"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
android:textSize="@dimen/body_font_size" android:textSize="@dimen/body_font_size"

View file

@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/button_height" android:layout_height="@dimen/button_height"
android:fontFamily="sans-serif-light" android:fontFamily="@font/lato"
android:gravity="center_vertical|start" android:gravity="center_vertical|start"
android:paddingLeft="@dimen/content_inset" android:paddingLeft="@dimen/content_inset"
android:paddingRight="@dimen/content_inset" android:paddingRight="@dimen/content_inset"

View file

@ -4,12 +4,10 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
> >
<ProgressBar <ProgressBar
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
style="?android:progressBarStyleLarge" style="?android:progressBarStyleLarge"
/> />
</merge> </merge>

View file

@ -9,6 +9,7 @@
<dimen name="content_inset_less">12dp</dimen> <dimen name="content_inset_less">12dp</dimen>
<dimen name="content_inset">16dp</dimen> <dimen name="content_inset">16dp</dimen>
<dimen name="content_inset_more">24dp</dimen> <dimen name="content_inset_more">24dp</dimen>
<dimen name="content_inset_double">32dp</dimen>
<dimen name="code_font_size">14sp</dimen> <dimen name="code_font_size">14sp</dimen>
<dimen name="body_font_size">14sp</dimen> <dimen name="body_font_size">14sp</dimen>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="com_google_android_gms_fonts_certs">
<item>@array/com_google_android_gms_fonts_certs_dev</item>
<item>@array/com_google_android_gms_fonts_certs_prod</item>
</array>
<string-array name="com_google_android_gms_fonts_certs_dev">
<item>
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
</item>
</string-array>
<string-array name="com_google_android_gms_fonts_certs_prod">
<item>
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
</item>
</string-array>
</resources>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="preloaded_fonts" translatable="false">
<item>@font/fira_mono</item>
<item>@font/lato</item>
<item>@font/lato_black</item>
<item>@font/lato_light</item>
</array>
</resources>