Begin to modularize reused view layouts, switch to Material Components theme

This commit is contained in:
Aidan Follestad 2018-11-30 15:34:40 -08:00
commit 6dfff5bb12
41 changed files with 502 additions and 365 deletions

1
.idea/modules.xml generated
View file

@ -8,6 +8,7 @@
<module fileurl="file://$PROJECT_DIR$/nock-nock.iml" filepath="$PROJECT_DIR$/nock-nock.iml" /> <module fileurl="file://$PROJECT_DIR$/nock-nock.iml" filepath="$PROJECT_DIR$/nock-nock.iml" />
<module fileurl="file://$PROJECT_DIR$/notifications/notifications.iml" filepath="$PROJECT_DIR$/notifications/notifications.iml" /> <module fileurl="file://$PROJECT_DIR$/notifications/notifications.iml" filepath="$PROJECT_DIR$/notifications/notifications.iml" />
<module fileurl="file://$PROJECT_DIR$/utilities/utilities.iml" filepath="$PROJECT_DIR$/utilities/utilities.iml" /> <module fileurl="file://$PROJECT_DIR$/utilities/utilities.iml" filepath="$PROJECT_DIR$/utilities/utilities.iml" />
<module fileurl="file://$PROJECT_DIR$/viewcomponents/viewcomponents.iml" filepath="$PROJECT_DIR$/viewcomponents/viewcomponents.iml" />
</modules> </modules>
</component> </component>
</project> </project>

View file

@ -22,6 +22,7 @@ dependencies {
implementation project(':utilities') implementation project(':utilities')
implementation project(':engine') implementation project(':engine')
implementation project(':notifications') implementation project(':notifications')
implementation project(':viewcomponents')
implementation 'androidx.appcompat:appcompat:' + versions.androidx implementation 'androidx.appcompat:appcompat:' + versions.androidx
implementation 'androidx.recyclerview:recyclerview:' + versions.androidx implementation 'androidx.recyclerview:recyclerview:' + versions.androidx

View file

@ -25,33 +25,30 @@ 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.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.conceal
import com.afollestad.nocknock.utilities.ext.hide
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.onEnd
import com.afollestad.nocknock.utilities.ext.onItemSelected
import com.afollestad.nocknock.utilities.ext.onLayout
import com.afollestad.nocknock.utilities.ext.scopeWhileAttached import com.afollestad.nocknock.utilities.ext.scopeWhileAttached
import com.afollestad.nocknock.utilities.ext.show import com.afollestad.nocknock.viewcomponents.ext.conceal
import com.afollestad.nocknock.utilities.ext.showOrHide import com.afollestad.nocknock.viewcomponents.ext.hide
import com.afollestad.nocknock.utilities.ext.textAsLong import com.afollestad.nocknock.viewcomponents.ext.onItemSelected
import com.afollestad.nocknock.utilities.ext.trimmedText import com.afollestad.nocknock.viewcomponents.ext.onLayout
import kotlinx.android.synthetic.main.activity_addsite.checkIntervalInput import com.afollestad.nocknock.viewcomponents.ext.show
import kotlinx.android.synthetic.main.activity_addsite.checkIntervalSpinner import com.afollestad.nocknock.viewcomponents.ext.showOrHide
import kotlinx.android.synthetic.main.activity_addsite.content_loading_progress import com.afollestad.nocknock.viewcomponents.ext.trimmedText
import kotlinx.android.synthetic.main.activity_addsite.checkIntervalLayout
import kotlinx.android.synthetic.main.activity_addsite.doneBtn import kotlinx.android.synthetic.main.activity_addsite.doneBtn
import kotlinx.android.synthetic.main.activity_addsite.inputName import kotlinx.android.synthetic.main.activity_addsite.inputName
import kotlinx.android.synthetic.main.activity_addsite.inputUrl import kotlinx.android.synthetic.main.activity_addsite.inputUrl
import kotlinx.android.synthetic.main.activity_addsite.loadingProgress
import kotlinx.android.synthetic.main.activity_addsite.nameTiLayout import kotlinx.android.synthetic.main.activity_addsite.nameTiLayout
import kotlinx.android.synthetic.main.activity_addsite.responseValidationMode import kotlinx.android.synthetic.main.activity_addsite.responseValidationMode
import kotlinx.android.synthetic.main.activity_addsite.responseValidationSearchTerm import kotlinx.android.synthetic.main.activity_addsite.responseValidationSearchTerm
import kotlinx.android.synthetic.main.activity_addsite.rootView import kotlinx.android.synthetic.main.activity_addsite.rootView
import kotlinx.android.synthetic.main.activity_addsite.scriptInputLayout
import kotlinx.android.synthetic.main.activity_addsite.textUrlWarning import kotlinx.android.synthetic.main.activity_addsite.textUrlWarning
import kotlinx.android.synthetic.main.activity_addsite.toolbar import kotlinx.android.synthetic.main.activity_addsite.toolbar
import kotlinx.android.synthetic.main.activity_addsite.urlTiLayout import kotlinx.android.synthetic.main.activity_addsite.urlTiLayout
import kotlinx.android.synthetic.main.activity_addsite.validationModeDescription import kotlinx.android.synthetic.main.activity_addsite.validationModeDescription
import kotlinx.android.synthetic.main.include_script_input.responseValidationScript
import kotlinx.android.synthetic.main.include_script_input.responseValidationScriptInput
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
@ -97,14 +94,6 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener {
rootView.onLayout { circularRevealActivity() } rootView.onLayout { circularRevealActivity() }
} }
val intervalOptionsAdapter = ArrayAdapter(
this,
R.layout.list_item_spinner,
resources.getStringArray(R.array.interval_options)
)
intervalOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown)
checkIntervalSpinner.adapter = intervalOptionsAdapter
inputUrl.setOnFocusChangeListener { _, hasFocus -> inputUrl.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) { if (!hasFocus) {
val inputStr = inputUrl.text val inputStr = inputUrl.text
@ -137,7 +126,7 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener {
responseValidationMode.adapter = validationOptionsAdapter responseValidationMode.adapter = validationOptionsAdapter
responseValidationMode.onItemSelected { pos -> responseValidationMode.onItemSelected { pos ->
responseValidationSearchTerm.showOrHide(pos == 1) responseValidationSearchTerm.showOrHide(pos == 1)
responseValidationScript.showOrHide(pos == 2) scriptInputLayout.showOrHide(pos == 2)
validationModeDescription.setText( validationModeDescription.setText(
when (pos) { when (pos) {
@ -200,14 +189,14 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener {
// Done button // Done button
override fun onClick(view: View) { override fun onClick(view: View) {
isClosing = true isClosing = true
var model = ServerModel( var newModel = ServerModel(
name = inputName.trimmedText(), name = inputName.trimmedText(),
url = inputUrl.trimmedText(), url = inputUrl.trimmedText(),
status = WAITING, status = WAITING,
validationMode = STATUS_CODE validationMode = STATUS_CODE
) )
if (model.name.isEmpty()) { if (newModel.name.isEmpty()) {
nameTiLayout.error = getString(R.string.please_enter_name) nameTiLayout.error = getString(R.string.please_enter_name)
isClosing = false isClosing = false
return return
@ -215,42 +204,42 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener {
nameTiLayout.error = null nameTiLayout.error = null
} }
if (model.url.isEmpty()) { if (newModel.url.isEmpty()) {
urlTiLayout.error = getString(R.string.please_enter_url) urlTiLayout.error = getString(R.string.please_enter_url)
isClosing = false isClosing = false
return return
} else { } else {
urlTiLayout.error = null urlTiLayout.error = null
if (!WEB_URL.matcher(model.url).find()) { if (!WEB_URL.matcher(newModel.url).find()) {
urlTiLayout.error = getString(R.string.please_enter_valid_url) urlTiLayout.error = getString(R.string.please_enter_valid_url)
isClosing = false isClosing = false
return return
} else { } else {
val uri = Uri.parse(model.url) val uri = Uri.parse(newModel.url)
if (uri.scheme == null) { if (uri.scheme == null) {
model = model.copy(url = "http://${model.url}") newModel = newModel.copy(url = "http://${newModel.url}")
} }
} }
} }
val parsedCheckInterval = getParsedCheckInterval() val selectedCheckInterval = checkIntervalLayout.getSelectedCheckInterval()
val selectedValidationMode = getSelectedValidationMode() val selectedValidationMode = getSelectedValidationMode()
val selectedValidationContent = getSelectedValidationContent() val selectedValidationContent = getSelectedValidationContent()
model = model.copy( newModel = newModel.copy(
checkInterval = parsedCheckInterval, checkInterval = selectedCheckInterval,
lastCheck = currentTimeMillis() - parsedCheckInterval, lastCheck = currentTimeMillis() - selectedCheckInterval,
validationMode = selectedValidationMode, validationMode = selectedValidationMode,
validationContent = selectedValidationContent validationContent = selectedValidationContent
) )
rootView.scopeWhileAttached(Main) { rootView.scopeWhileAttached(Main) {
launch(coroutineContext) { launch(coroutineContext) {
content_loading_progress.show() loadingProgress.setLoading()
val storedModel = async(IO) { serverModelStore.put(model) }.await() val storedModel = async(IO) { serverModelStore.put(newModel) }.await()
checkStatusManager.cancelCheck(storedModel) checkStatusManager.cancelCheck(storedModel)
checkStatusManager.scheduleCheck(storedModel, rightNow = true) checkStatusManager.scheduleCheck(storedModel, rightNow = true)
content_loading_progress.hide() loadingProgress.setDone()
setResult(RESULT_OK) setResult(RESULT_OK)
finish() finish()
@ -261,16 +250,6 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener {
override fun onBackPressed() = closeActivityWithReveal() override fun onBackPressed() = closeActivityWithReveal()
private fun getParsedCheckInterval(): Long {
val intervalInput = checkIntervalInput.textAsLong()
return when (checkIntervalSpinner.selectedItemPosition) {
0 -> intervalInput * (60 * 1000)
1 -> intervalInput * (60 * 60 * 1000)
2 -> intervalInput * (60 * 60 * 24 * 1000)
else -> intervalInput * (60 * 60 * 24 * 7 * 1000)
}
}
private fun getSelectedValidationMode() = when (responseValidationMode.selectedItemPosition) { private fun getSelectedValidationMode() = when (responseValidationMode.selectedItemPosition) {
0 -> STATUS_CODE 0 -> STATUS_CODE
1 -> TERM_SEARCH 1 -> TERM_SEARCH
@ -285,7 +264,7 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener {
private fun getSelectedValidationContent() = when (responseValidationMode.selectedItemPosition) { private fun getSelectedValidationContent() = when (responseValidationMode.selectedItemPosition) {
0 -> null 0 -> null
1 -> responseValidationSearchTerm.trimmedText() 1 -> responseValidationSearchTerm.trimmedText()
2 -> responseValidationScriptInput.trimmedText() 2 -> scriptInputLayout.getCode()
else -> { else -> {
throw IllegalStateException( throw IllegalStateException(
"Unexpected validation mode index: ${responseValidationMode.selectedItemPosition}" "Unexpected validation mode index: ${responseValidationMode.selectedItemPosition}"

View file

@ -44,8 +44,8 @@ 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.utilities.ext.show import com.afollestad.nocknock.viewcomponents.ext.show
import com.afollestad.nocknock.utilities.ext.showOrHide import com.afollestad.nocknock.viewcomponents.ext.showOrHide
import com.afollestad.nocknock.utilities.util.MathUtil.bezierCurve import com.afollestad.nocknock.utilities.util.MathUtil.bezierCurve
import kotlinx.android.synthetic.main.activity_main.emptyText import kotlinx.android.synthetic.main.activity_main.emptyText
import kotlinx.android.synthetic.main.activity_main.fab import kotlinx.android.synthetic.main.activity_main.fab

View file

@ -35,46 +35,38 @@ 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
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.DAY
import com.afollestad.nocknock.utilities.ext.HOUR
import com.afollestad.nocknock.utilities.ext.MINUTE
import com.afollestad.nocknock.utilities.ext.WEEK
import com.afollestad.nocknock.utilities.ext.formatDate import com.afollestad.nocknock.utilities.ext.formatDate
import com.afollestad.nocknock.utilities.ext.hide
import com.afollestad.nocknock.utilities.ext.injector import com.afollestad.nocknock.utilities.ext.injector
import com.afollestad.nocknock.utilities.ext.isHttpOrHttps import com.afollestad.nocknock.utilities.ext.isHttpOrHttps
import com.afollestad.nocknock.utilities.ext.onItemSelected
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.utilities.ext.show import com.afollestad.nocknock.viewcomponents.ext.hide
import com.afollestad.nocknock.utilities.ext.showOrHide import com.afollestad.nocknock.viewcomponents.ext.onItemSelected
import com.afollestad.nocknock.utilities.ext.textAsLong import com.afollestad.nocknock.viewcomponents.ext.show
import com.afollestad.nocknock.utilities.ext.trimmedText import com.afollestad.nocknock.viewcomponents.ext.showOrHide
import kotlinx.android.synthetic.main.activity_viewsite.checkIntervalInput import com.afollestad.nocknock.viewcomponents.ext.trimmedText
import kotlinx.android.synthetic.main.activity_viewsite.checkIntervalSpinner import kotlinx.android.synthetic.main.activity_viewsite.checkIntervalLayout
import kotlinx.android.synthetic.main.activity_viewsite.content_loading_progress
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
import kotlinx.android.synthetic.main.activity_viewsite.inputUrl import kotlinx.android.synthetic.main.activity_viewsite.inputUrl
import kotlinx.android.synthetic.main.activity_viewsite.loadingProgress
import kotlinx.android.synthetic.main.activity_viewsite.responseValidationMode 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.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
import kotlinx.android.synthetic.main.activity_viewsite.toolbar import kotlinx.android.synthetic.main.activity_viewsite.toolbar
import kotlinx.android.synthetic.main.activity_viewsite.validationModeDescription import kotlinx.android.synthetic.main.activity_viewsite.validationModeDescription
import kotlinx.android.synthetic.main.include_script_input.responseValidationScript
import kotlinx.android.synthetic.main.include_script_input.responseValidationScriptInput
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
import kotlinx.coroutines.launch 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.ceil
private const val KEY_VIEW_MODEL = "site_model" private const val KEY_VIEW_MODEL = "site_model"
@ -130,14 +122,6 @@ class ViewSiteActivity : AppCompatActivity(),
setOnMenuItemClickListener(this@ViewSiteActivity) setOnMenuItemClickListener(this@ViewSiteActivity)
} }
val intervalOptionsAdapter = ArrayAdapter(
this,
R.layout.list_item_spinner,
resources.getStringArray(R.array.interval_options)
)
intervalOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown)
checkIntervalSpinner.adapter = intervalOptionsAdapter
inputUrl.setOnFocusChangeListener { _, hasFocus -> inputUrl.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) { if (!hasFocus) {
val inputStr = inputUrl.text val inputStr = inputUrl.text
@ -170,7 +154,7 @@ class ViewSiteActivity : AppCompatActivity(),
responseValidationMode.onItemSelected { pos -> responseValidationMode.onItemSelected { pos ->
responseValidationSearchTerm.showOrHide(pos == 1) responseValidationSearchTerm.showOrHide(pos == 1)
responseValidationScript.showOrHide(pos == 2) scriptInputLayout.showOrHide(pos == 2)
validationModeDescription.setText( validationModeDescription.setText(
when (pos) { when (pos) {
@ -213,45 +197,13 @@ class ViewSiteActivity : AppCompatActivity(),
if (this.checkInterval == 0L) { if (this.checkInterval == 0L) {
textNextCheck.setText(R.string.none_turned_off) textNextCheck.setText(R.string.none_turned_off)
checkIntervalInput.setText("") checkIntervalLayout.clear()
checkIntervalSpinner.setSelection(0)
} else { } else {
var lastCheck = this.lastCheck var lastCheck = this.lastCheck
if (lastCheck == 0L) { if (lastCheck == 0L) {
lastCheck = currentTimeMillis() lastCheck = currentTimeMillis()
} }
textNextCheck.text = (lastCheck + this.checkInterval).formatDate() textNextCheck.text = (lastCheck + this.checkInterval).formatDate()
when {
this.checkInterval >= WEEK -> {
checkIntervalInput.setText(
ceil((this.checkInterval.toFloat() / WEEK).toDouble()).toInt().toString()
)
checkIntervalSpinner.setSelection(3)
}
this.checkInterval >= DAY -> {
checkIntervalInput.setText(
ceil((this.checkInterval.toFloat() / DAY.toFloat()).toDouble()).toInt().toString()
)
checkIntervalSpinner.setSelection(2)
}
this.checkInterval >= HOUR -> {
checkIntervalInput.setText(
ceil((this.checkInterval.toFloat() / HOUR.toFloat()).toDouble()).toInt().toString()
)
checkIntervalSpinner.setSelection(1)
}
this.checkInterval >= MINUTE -> {
checkIntervalInput.setText(
ceil((this.checkInterval.toFloat() / MINUTE.toFloat()).toDouble()).toInt().toString()
)
checkIntervalSpinner.setSelection(0)
}
else -> {
checkIntervalInput.setText("0")
checkIntervalSpinner.setSelection(0)
}
}
} }
responseValidationMode.setSelection(validationMode.value - 1) responseValidationMode.setSelection(validationMode.value - 1)
@ -259,13 +211,11 @@ class ViewSiteActivity : AppCompatActivity(),
when (this.validationMode) { when (this.validationMode) {
TERM_SEARCH -> responseValidationSearchTerm.setText(this.validationContent ?: "") TERM_SEARCH -> responseValidationSearchTerm.setText(this.validationContent ?: "")
JAVASCRIPT -> { JAVASCRIPT -> {
responseValidationScriptInput.setText( scriptInputLayout.setCode(this.validationContent)
this.validationContent ?: getString(R.string.default_js)
)
} }
else -> { else -> {
responseValidationSearchTerm.setText("") responseValidationSearchTerm.setText("")
responseValidationScriptInput.setText("") scriptInputLayout.clear()
} }
} }
@ -316,13 +266,13 @@ class ViewSiteActivity : AppCompatActivity(),
} }
} }
val parsedCheckInterval = getParsedCheckInterval() val selectedCheckInterval = checkIntervalLayout.getSelectedCheckInterval()
val selectedValidationMode = getSelectedValidationMode() val selectedValidationMode = getSelectedValidationMode()
val selectedValidationContent = getSelectedValidationContent() val selectedValidationContent = getSelectedValidationContent()
currentModel = currentModel.copy( currentModel = currentModel.copy(
checkInterval = parsedCheckInterval, checkInterval = selectedCheckInterval,
lastCheck = currentTimeMillis() - parsedCheckInterval, lastCheck = currentTimeMillis() - selectedCheckInterval,
validationMode = selectedValidationMode, validationMode = selectedValidationMode,
validationContent = selectedValidationContent validationContent = selectedValidationContent
) )
@ -334,10 +284,10 @@ class ViewSiteActivity : AppCompatActivity(),
override fun onClick(view: View) { override fun onClick(view: View) {
rootView.scopeWhileAttached(Main) { rootView.scopeWhileAttached(Main) {
launch(coroutineContext) { launch(coroutineContext) {
content_loading_progress.show() loadingProgress.setLoading()
if (!updateModelFromInput(true)) { if (!updateModelFromInput(true)) {
// Validation didn't pass // Validation didn't pass
content_loading_progress.hide() loadingProgress.setDone()
return@launch return@launch
} }
@ -345,7 +295,7 @@ class ViewSiteActivity : AppCompatActivity(),
checkStatusManager.cancelCheck(currentModel) checkStatusManager.cancelCheck(currentModel)
checkStatusManager.scheduleCheck(currentModel, rightNow = true) checkStatusManager.scheduleCheck(currentModel, rightNow = true)
content_loading_progress.hide() loadingProgress.setDone()
setResult(RESULT_OK) setResult(RESULT_OK)
finish() finish()
} }
@ -357,7 +307,7 @@ class ViewSiteActivity : AppCompatActivity(),
R.id.refresh -> { R.id.refresh -> {
rootView.scopeWhileAttached(Main) { rootView.scopeWhileAttached(Main) {
launch(coroutineContext) { launch(coroutineContext) {
content_loading_progress.show() loadingProgress.setLoading()
updateModelFromInput(false) updateModelFromInput(false)
currentModel = currentModel.copy(status = WAITING) currentModel = currentModel.copy(status = WAITING)
displayCurrentModel() displayCurrentModel()
@ -366,7 +316,7 @@ class ViewSiteActivity : AppCompatActivity(),
checkStatusManager.cancelCheck(currentModel) checkStatusManager.cancelCheck(currentModel)
checkStatusManager.scheduleCheck(currentModel, rightNow = true) checkStatusManager.scheduleCheck(currentModel, rightNow = true)
content_loading_progress.hide() loadingProgress.setDone()
} }
} }
return true return true
@ -400,9 +350,9 @@ class ViewSiteActivity : AppCompatActivity(),
private fun performRemoveSite(model: ServerModel) { private fun performRemoveSite(model: ServerModel) {
rootView.scopeWhileAttached(Main) { rootView.scopeWhileAttached(Main) {
launch(coroutineContext) { launch(coroutineContext) {
content_loading_progress.show() loadingProgress.setLoading()
async(IO) { serverModelStore.delete(model) }.await() async(IO) { serverModelStore.delete(model) }.await()
content_loading_progress.hide() loadingProgress.setDone()
finish() finish()
} }
} }
@ -413,16 +363,6 @@ class ViewSiteActivity : AppCompatActivity(),
item.isEnabled = currentModel.status != CHECKING && currentModel.status != WAITING item.isEnabled = currentModel.status != CHECKING && currentModel.status != WAITING
} }
private fun getParsedCheckInterval(): Long {
val intervalInput = checkIntervalInput.textAsLong()
return when (checkIntervalSpinner.selectedItemPosition) {
0 -> intervalInput * (60 * 1000)
1 -> intervalInput * (60 * 60 * 1000)
2 -> intervalInput * (60 * 60 * 24 * 1000)
else -> intervalInput * (60 * 60 * 24 * 7 * 1000)
}
}
private fun getSelectedValidationMode() = when (responseValidationMode.selectedItemPosition) { private fun getSelectedValidationMode() = when (responseValidationMode.selectedItemPosition) {
0 -> STATUS_CODE 0 -> STATUS_CODE
1 -> TERM_SEARCH 1 -> TERM_SEARCH
@ -437,7 +377,7 @@ class ViewSiteActivity : AppCompatActivity(),
private fun getSelectedValidationContent() = when (responseValidationMode.selectedItemPosition) { private fun getSelectedValidationContent() = when (responseValidationMode.selectedItemPosition) {
0 -> null 0 -> null
1 -> responseValidationSearchTerm.trimmedText() 1 -> responseValidationSearchTerm.trimmedText()
2 -> responseValidationScriptInput.trimmedText() 2 -> scriptInputLayout.getCode()
else -> { else -> {
throw IllegalStateException( throw IllegalStateException(
"Unexpected validation mode index: ${responseValidationMode.selectedItemPosition}" "Unexpected validation mode index: ${responseValidationMode.selectedItemPosition}"

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/md_green"/>
<stroke android:color="#424242"/>
<size
android:width="@dimen/list_circle_size"
android:height="@dimen/list_circle_size"/>
</shape>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/md_red"/>
<stroke android:color="#424242"/>
<size
android:width="@dimen/list_circle_size"
android:height="@dimen/list_circle_size"/>
</shape>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/md_yellow"/>
<stroke android:color="#424242"/>
<size
android:width="@dimen/list_circle_size"
android:height="@dimen/list_circle_size"/>
</shape>

View file

@ -96,62 +96,13 @@
tools:text="Warning: this app checks for server availability with HTTP requests. It's recommended that you use an HTTP URL." tools:text="Warning: this app checks for server availability with HTTP requests. It's recommended that you use an HTTP URL."
/> />
<View <include layout="@layout/include_divider"/>
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/content_inset"
android:background="@color/dividerColorDark"
/>
<TextView <com.afollestad.nocknock.viewcomponents.CheckIntervalLayout
android:id="@+id/checkIntervalLabel" android:id="@+id/checkIntervalLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset"
android:fontFamily="sans-serif"
android:text="@string/check_interval"
android:textColor="?colorAccent"
android:textSize="@dimen/caption_font_size"
/>
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2"
>
<EditText
android:id="@+id/checkIntervalInput"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginEnd="@dimen/content_inset_half"
android:layout_marginStart="-4dp"
android:layout_weight="1"
android:fontFamily="sans-serif-light"
android:hint="0"
android:inputType="number"
android:textSize="@dimen/body_font_size"
tools:ignore="HardcodedText,LabelFor"
/>
<Spinner
android:id="@+id/checkIntervalSpinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="-4dp"
android:layout_weight="1"
/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/content_inset" android:layout_marginTop="@dimen/content_inset"
android:background="@color/dividerColorDark"
/> />
<TextView <TextView
@ -186,7 +137,14 @@
tools:ignore="Autofill" tools:ignore="Autofill"
/> />
<include layout="@layout/include_script_input"/> <com.afollestad.nocknock.viewcomponents.JavaScriptInputLayout
android:id="@+id/scriptInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset"
android:layout_marginTop="@dimen/content_inset_half"
android:background="@color/colorPrimaryDark"
/>
<TextView <TextView
android:id="@+id/validationModeDescription" android:id="@+id/validationModeDescription"
@ -199,15 +157,13 @@
android:textSize="@dimen/body_font_size" android:textSize="@dimen/body_font_size"
/> />
<Button <com.google.android.material.button.MaterialButton
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_marginLeft="-4dp"
android:layout_marginRight="-4dp"
android:layout_marginTop="@dimen/content_inset" android:layout_marginTop="@dimen/content_inset"
android:text="@string/done" android:text="@string/done"
android:textColor="#fff" style="@style/AccentButton"
/> />
</LinearLayout> </LinearLayout>
@ -216,21 +172,10 @@
</LinearLayout> </LinearLayout>
<FrameLayout <com.afollestad.nocknock.viewcomponents.LoadingIndicatorFrame
android:id="@+id/content_loading_progress" android:id="@+id/loadingProgress"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#40000000"
android:visibility="gone"
>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
style="?android:progressBarStyleLarge"
/> />
</FrameLayout> </FrameLayout>
</FrameLayout>

View file

@ -45,7 +45,7 @@
android:orientation="horizontal" android:orientation="horizontal"
> >
<com.afollestad.nocknock.views.StatusImageView <com.afollestad.nocknock.viewcomponents.StatusImageView
android:id="@+id/iconStatus" android:id="@+id/iconStatus"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -110,57 +110,20 @@
</LinearLayout> </LinearLayout>
<View <include
layout="@layout/include_divider"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_marginTop="@dimen/content_inset_less" android:layout_marginTop="@dimen/content_inset_less"
android:background="@color/dividerColorDark"
/> />
<TextView <com.afollestad.nocknock.viewcomponents.CheckIntervalLayout
android:id="@+id/checkIntervalLabel" android:id="@+id/checkIntervalLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset"
android:fontFamily="sans-serif"
android:text="@string/check_interval"
android:textColor="?colorAccent"
android:textSize="@dimen/caption_font_size"
/>
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:layout_marginTop="@dimen/content_inset"
android:weightSum="2"
>
<EditText
android:id="@+id/checkIntervalInput"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginEnd="@dimen/content_inset_half"
android:layout_marginStart="-4dp"
android:layout_weight="1"
android:fontFamily="sans-serif-light"
android:hint="0"
android:inputType="number"
android:textSize="@dimen/body_font_size"
tools:ignore="HardcodedText,LabelFor"
/> />
<Spinner
android:id="@+id/checkIntervalSpinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="-4dp"
android:layout_weight="1"
/>
</LinearLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
@ -197,9 +160,17 @@
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,TextFields"
/> />
<include layout="@layout/include_script_input"/> <com.afollestad.nocknock.viewcomponents.JavaScriptInputLayout
android:id="@+id/scriptInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset"
android:layout_marginTop="@dimen/content_inset_half"
android:background="@color/colorPrimaryDark"
/>
<TextView <TextView
android:id="@+id/validationModeDescription" android:id="@+id/validationModeDescription"
@ -212,12 +183,7 @@
android:textSize="@dimen/body_font_size" android:textSize="@dimen/body_font_size"
/> />
<View <include layout="@layout/include_divider"/>
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/content_inset"
android:background="@color/dividerColorDark"
/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -259,15 +225,13 @@
tools:text="In 2 hours" tools:text="In 2 hours"
/> />
<Button <com.google.android.material.button.MaterialButton
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_marginLeft="-4dp"
android:layout_marginRight="-4dp"
android:layout_marginTop="@dimen/content_inset_more" android:layout_marginTop="@dimen/content_inset_more"
android:text="@string/save" android:text="@string/save"
android:textColor="#fff" style="@style/AccentButton"
/> />
</LinearLayout> </LinearLayout>
@ -276,21 +240,10 @@
</LinearLayout> </LinearLayout>
<FrameLayout <com.afollestad.nocknock.viewcomponents.LoadingIndicatorFrame
android:id="@+id/content_loading_progress" android:id="@+id/loadingProgress"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#40000000"
android:visibility="gone"
>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
style="?android:progressBarStyleLarge"
/> />
</FrameLayout> </FrameLayout>
</FrameLayout>

View file

@ -12,7 +12,7 @@
android:paddingTop="@dimen/content_inset_less" android:paddingTop="@dimen/content_inset_less"
> >
<com.afollestad.nocknock.views.StatusImageView <com.afollestad.nocknock.viewcomponents.StatusImageView
android:id="@+id/iconStatus" android:id="@+id/iconStatus"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -1,13 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string-array name="interval_options">
<item>Minute(s)</item>
<item>Hour(s)</item>
<item>Day(s)</item>
<item>Week(s)</item>
</string-array>
<string-array name="site_long_options" translatable="false"> <string-array name="site_long_options" translatable="false">
<item>@string/refresh_status</item> <item>@string/refresh_status</item>
<item>@string/remove_site</item> <item>@string/remove_site</item>

View file

@ -7,10 +7,4 @@
<color name="dividerColor">#EEEEEE</color> <color name="dividerColor">#EEEEEE</color>
<color name="md_red">#E53935</color>
<color name="md_yellow">#FDD835</color>
<color name="md_green">#43A047</color>
<color name="dividerColorDark">#37474F</color>
</resources> </resources>

View file

@ -2,20 +2,11 @@
<dimen name="title_font_size">20sp</dimen> <dimen name="title_font_size">20sp</dimen>
<dimen name="medium_text_size">16sp</dimen> <dimen name="medium_text_size">16sp</dimen>
<dimen name="body_font_size">14sp</dimen>
<dimen name="caption_font_size">12sp</dimen>
<dimen name="empty_text_size">26sp</dimen> <dimen name="empty_text_size">26sp</dimen>
<dimen name="content_inset_half">8dp</dimen>
<dimen name="content_inset_less">12dp</dimen>
<dimen name="content_inset">16dp</dimen>
<dimen name="content_inset_more">24dp</dimen>
<dimen name="list_circle_size">42dp</dimen>
<dimen name="list_text_spacing">4dp</dimen> <dimen name="list_text_spacing">4dp</dimen>
<dimen name="fab_elevation">4dp</dimen> <dimen name="fab_elevation">4dp</dimen>
<dimen name="fab_elevation_pressed">8dp</dimen> <dimen name="fab_elevation_pressed">8dp</dimen>
<dimen name="button_height">52dp</dimen>
<dimen name="code_font_size">14sp</dimen>
</resources> </resources>

View file

@ -19,7 +19,6 @@
<string name="add_site">Add Site</string> <string name="add_site">Add Site</string>
<string name="site_name">Site Name</string> <string name="site_name">Site Name</string>
<string name="site_url">Site URL</string> <string name="site_url">Site URL</string>
<string name="check_interval">Check Interval</string>
<string name="done">Done</string> <string name="done">Done</string>
<string name="please_enter_name">Please enter a name!</string> <string name="please_enter_name">Please enter a name!</string>
<string name="please_enter_url">Please enter a URL.</string> <string name="please_enter_url">Please enter a URL.</string>
@ -42,9 +41,6 @@
<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="default_js">var responseObj = JSON.parse(response);\nreturn responseObj.success === true;</string>
<string name="function_declaration">function validate(response) {</string>
<string name="function_end">}</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>

View file

@ -1,6 +1,6 @@
<resources> <resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<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>
@ -12,7 +12,7 @@
<item name="android:textColorSecondary">#727272</item> <item name="android:textColorSecondary">#727272</item>
</style> </style>
<style name="AppTheme.Ink" parent="Theme.AppCompat.NoActionBar"> <style name="AppTheme.Ink" parent="Theme.MaterialComponents.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,7 +27,7 @@
<item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowBackground">@android:color/transparent</item>
</style> </style>
<style name="AccentButton" parent="Widget.AppCompat.Button.Colored"> <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="android:colorButtonNormal">@color/colorAccent</item>
</style> </style>

View file

@ -1 +1 @@
include ':app', ':engine', ':notifications', ':data', ':utilities' include ':app', ':engine', ':notifications', ':data', ':utilities', ':viewcomponents'

1
viewcomponents/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,29 @@
apply from: '../dependencies.gradle'
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion versions.compileSdk
defaultConfig {
minSdkVersion versions.minSdk
targetSdkVersion versions.compileSdk
versionCode versions.publishVersionCode
versionName versions.publishVersion
}
}
dependencies {
implementation project(':utilities')
implementation project(':data')
implementation 'androidx.appcompat:appcompat:' + versions.androidx
api 'com.squareup.okhttp3:okhttp:' + versions.okHttp
implementation 'com.google.dagger:dagger:' + versions.dagger
kapt 'com.google.dagger:dagger-compiler:' + versions.dagger
}
apply from: '../spotless.gradle'

View file

@ -0,0 +1,2 @@
<manifest
package="com.afollestad.nocknock.viewcomponents"/>

View file

@ -0,0 +1,103 @@
/*
* Licensed under Apache-2.0
*
* Designed and developed by Aidan Follestad (@afollestad)
*/
package com.afollestad.nocknock.viewcomponents
import android.content.Context
import android.util.AttributeSet
import android.widget.ArrayAdapter
import android.widget.LinearLayout
import androidx.annotation.CheckResult
import com.afollestad.nocknock.utilities.ext.DAY
import com.afollestad.nocknock.utilities.ext.HOUR
import com.afollestad.nocknock.utilities.ext.MINUTE
import com.afollestad.nocknock.utilities.ext.WEEK
import com.afollestad.nocknock.viewcomponents.R.array
import com.afollestad.nocknock.viewcomponents.ext.textAsLong
import kotlinx.android.synthetic.main.check_interval_layout.view.input
import kotlinx.android.synthetic.main.check_interval_layout.view.spinner
import kotlin.math.ceil
/** @author Aidan Follestad (afollestad) */
class CheckIntervalLayout(
context: Context,
attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {
companion object {
private const val INDEX_MINUTE = 0
private const val INDEX_HOUR = 1
private const val INDEX_DAY = 2
private const val INDEX_WEEK = 3
}
init {
orientation = VERTICAL
inflate(context, R.layout.check_interval_layout, this)
}
override fun onFinishInflate() {
super.onFinishInflate()
val spinnerAdapter = ArrayAdapter(
context,
R.layout.list_item_spinner,
resources.getStringArray(array.interval_options)
)
spinnerAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown)
spinner.adapter = spinnerAdapter
}
fun set(interval: Long) {
when {
interval >= WEEK -> {
input.setText(calculateDisplayValue(interval, WEEK))
spinner.setSelection(3)
}
interval >= DAY -> {
input.setText(calculateDisplayValue(interval, DAY))
spinner.setSelection(2)
}
interval >= HOUR -> {
input.setText(calculateDisplayValue(interval, HOUR))
spinner.setSelection(1)
}
interval >= MINUTE -> {
input.setText(calculateDisplayValue(interval, MINUTE))
spinner.setSelection(0)
}
else -> {
input.setText("0")
spinner.setSelection(0)
}
}
}
fun clear() {
input.setText("")
spinner.setSelection(0)
}
@CheckResult fun getSelectedCheckInterval(): Long {
val intervalInput = input.textAsLong()
val spinnerPos = spinner.selectedItemPosition
return when (spinnerPos) {
INDEX_MINUTE -> intervalInput * MINUTE
INDEX_HOUR -> intervalInput * HOUR
INDEX_DAY -> intervalInput * DAY
INDEX_WEEK -> intervalInput * WEEK
else -> throw IllegalStateException("Unexpected index: $spinnerPos")
}
}
private fun calculateDisplayValue(
interval: Long,
by: Long
): String {
val intervalFloat = interval.toFloat()
val byFloat = by.toFloat()
return ceil(intervalFloat / byFloat).toInt()
.toString()
}
}

View file

@ -0,0 +1,49 @@
/*
* Licensed under Apache-2.0
*
* Designed and developed by Aidan Follestad (@afollestad)
*/
package com.afollestad.nocknock.viewcomponents
import android.content.Context
import android.util.AttributeSet
import android.widget.HorizontalScrollView
import androidx.annotation.CheckResult
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
import com.afollestad.nocknock.viewcomponents.ext.dimenInt
import com.afollestad.nocknock.viewcomponents.ext.trimmedText
import kotlinx.android.synthetic.main.javascript_input_layout.view.userInput
/** @author Aidan Follestad (afollestad) */
class JavaScriptInputLayout(
context: Context,
attrs: AttributeSet? = null
) : HorizontalScrollView(context, attrs) {
init {
val contentInset = dimenInt(R.dimen.content_inset)
val contentInsetHalf = dimenInt(R.dimen.content_inset_half)
setPadding(
contentInsetHalf, // left
contentInset, // top
contentInsetHalf, // right
contentInset // bottom
)
elevation = dimenFloat(R.dimen.default_elevation)
inflate(context, R.layout.javascript_input_layout, this)
}
fun setCode(code: String?) {
if (code.isNullOrEmpty()) {
setDefaultCode()
return
}
userInput.setText(code.trim())
}
fun setDefaultCode() = userInput.setText(R.string.default_js)
@CheckResult fun getCode() = userInput.trimmedText()
fun clear() = userInput.setText("")
}

View file

@ -0,0 +1,40 @@
/*
* Licensed under Apache-2.0
*
* Designed and developed by Aidan Follestad (@afollestad)
*/
package com.afollestad.nocknock.viewcomponents
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import com.afollestad.nocknock.viewcomponents.ext.hide
import com.afollestad.nocknock.viewcomponents.ext.show
/** @author Aidan Follestad (@afollestad) */
class LoadingIndicatorFrame(
context: Context,
attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
companion object {
private const val SHOW_DELAY_MS = 200L
}
private val showRunnable = Runnable { show() }
init {
setBackgroundColor(ContextCompat.getColor(context, R.color.loading_indicator_frame_background))
hide() // hide self by default
inflate(context, R.layout.loading_indicator_frame, this)
}
fun setLoading() {
handler.postDelayed(showRunnable, SHOW_DELAY_MS)
}
fun setDone() {
handler.removeCallbacks(showRunnable)
hide()
}
}

View file

@ -3,12 +3,11 @@
* *
* Designed and developed by Aidan Follestad (@afollestad) * Designed and developed by Aidan Follestad (@afollestad)
*/ */
package com.afollestad.nocknock.views package com.afollestad.nocknock.viewcomponents
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatImageView
import com.afollestad.nocknock.R
import com.afollestad.nocknock.data.ServerStatus import com.afollestad.nocknock.data.ServerStatus
import com.afollestad.nocknock.data.ServerStatus.CHECKING import com.afollestad.nocknock.data.ServerStatus.CHECKING
import com.afollestad.nocknock.data.ServerStatus.ERROR import com.afollestad.nocknock.data.ServerStatus.ERROR

View file

@ -3,7 +3,7 @@
* *
* Designed and developed by Aidan Follestad (@afollestad) * Designed and developed by Aidan Follestad (@afollestad)
*/ */
package com.afollestad.nocknock.utilities.ext package com.afollestad.nocknock.viewcomponents.ext
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
@ -13,6 +13,7 @@ import android.view.ViewTreeObserver
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.Spinner import android.widget.Spinner
import android.widget.TextView import android.widget.TextView
import androidx.annotation.DimenRes
fun View.show() { fun View.show() {
visibility = VISIBLE visibility = VISIBLE
@ -59,3 +60,7 @@ fun View.onLayout(cb: () -> Unit) {
}) })
} }
} }
fun View.dimenFloat(@DimenRes res: Int) = resources.getDimension(res)
fun View.dimenInt(@DimenRes res: Int) = resources.getDimensionPixelSize(res)

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/md_green"/>
<stroke android:color="#424242"/>
<size
android:height="@dimen/list_circle_size"
android:width="@dimen/list_circle_size"/>
</shape>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/md_red"/>
<stroke android:color="#424242"/>
<size
android:height="@dimen/list_circle_size"
android:width="@dimen/list_circle_size"/>
</shape>

View file

@ -1,8 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp" android:height="24dp"
android:viewportHeight="24.0" android:viewportHeight="24.0"
android:viewportWidth="24.0"> android:viewportWidth="24.0"
android:width="24dp">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/> android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>

View file

@ -1,8 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp" android:height="24dp"
android:viewportHeight="24.0" android:viewportHeight="24.0"
android:viewportWidth="24.0"> android:viewportWidth="24.0"
android:width="24dp">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/> android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>

View file

@ -1,8 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp" android:height="24dp"
android:viewportHeight="24.0" android:viewportHeight="24.0"
android:viewportWidth="24.0"> android:viewportWidth="24.0"
android:width="24dp">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/> android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/md_yellow"/>
<stroke android:color="#424242"/>
<size
android:height="@dimen/list_circle_size"
android:width="@dimen/list_circle_size"/>
</shape>

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:text="@string/check_interval"
android:textColor="?colorAccent"
android:textSize="@dimen/caption_font_size"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2"
>
<EditText
android:id="@+id/input"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginEnd="@dimen/content_inset_half"
android:layout_marginStart="-4dp"
android:layout_weight="1"
android:fontFamily="sans-serif-light"
android:hint="0"
android:inputType="number"
android:textSize="@dimen/body_font_size"
tools:ignore="Autofill,HardcodedText,LabelFor"
/>
<Spinner
android:id="@+id/spinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="-4dp"
android:layout_weight="1"
/>
</LinearLayout>
</merge>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/content_inset"
android:background="@color/dividerColorDark"
/>

View file

@ -1,19 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView <merge
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/responseValidationScript"
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"
android:layout_marginTop="@dimen/content_inset_half"
android:background="@color/colorPrimaryDark"
android:elevation="@dimen/fab_elevation"
android:paddingBottom="@dimen/content_inset"
android:paddingLeft="@dimen/content_inset_half"
android:paddingRight="@dimen/content_inset_half"
android:paddingTop="@dimen/content_inset"
android:scrollbars="none"
tools:ignore="UnusedAttribute" tools:ignore="UnusedAttribute"
> >
@ -34,7 +24,7 @@
/> />
<EditText <EditText
android:id="@+id/responseValidationScriptInput" android:id="@+id/userInput"
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"
@ -62,4 +52,4 @@
</LinearLayout> </LinearLayout>
</HorizontalScrollView> </merge>

View file

@ -6,4 +6,5 @@
android:fontFamily="sans-serif-light" android:fontFamily="sans-serif-light"
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

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
style="?android:progressBarStyleLarge"
/>
</merge>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="interval_options">
<item>Minute(s)</item>
<item>Hour(s)</item>
<item>Day(s)</item>
<item>Week(s)</item>
</string-array>
</resources>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="dividerColorDark">#37474F</color>
<color name="loading_indicator_frame_background">#40000000</color>
<color name="md_red">#E53935</color>
<color name="md_yellow">#FDD835</color>
<color name="md_green">#43A047</color>
</resources>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="list_circle_size">42dp</dimen>
<dimen name="default_elevation">4dp</dimen>
<dimen name="content_inset_half">8dp</dimen>
<dimen name="content_inset_less">12dp</dimen>
<dimen name="content_inset">16dp</dimen>
<dimen name="content_inset_more">24dp</dimen>
<dimen name="code_font_size">14sp</dimen>
<dimen name="body_font_size">14sp</dimen>
<dimen name="caption_font_size">12sp</dimen>
<dimen name="button_height">52dp</dimen>
</resources>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="default_js">var responseObj = JSON.parse(response);\nreturn responseObj.success === true;</string>
<string name="function_declaration">function validate(response) {</string>
<string name="function_end">}</string>
<string name="check_interval">Check Interval</string>
</resources>