diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..17bc1fa
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,3 @@
+[*.kt]
+indent_size = 2
+continuation_indent_size=4
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..a574b92
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,28 @@
+(`[x]` becomes a filled in checkbox, `[ ]` is an empty one)
+
+- [ ] I have verified there are [no duplicate active or recent bugs, questions, or requests](https://github.com/afollestad/nock-nock/issues?q=is%3Aissue+is%3Aclosed)
+- [ ] I have given my issue a non-generic title.
+
+---
+
+If this is a improvement or feature request, you can remove everything below.
+Also, please consider making a pull request if you are capable of contributing.
+
+###### Include the following:
+
+ - Nock Nock version: `0.x.x`
+ - Affected device: Google Pixel 3 XL with Android 9.0
+
+---
+
+###### Reproduction Steps
+
+1.
+
+---
+
+###### Expected Result
+
+---
+
+###### Actual Result
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md
deleted file mode 100644
index 2d7d09f..0000000
--- a/.github/ISSUE_TEMPLATE/Bug_report.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-name: Bug report
-about: Something is crashing or not working as intended
-
----
-
-*Please consider making a Pull Request if you are capable of doing so.*
-
-**App Version:**
-
-x.x.x
-
-**Affected Device(s):**
-
-Google Pixel 3 XL with Android 9.0
-
-**Describe the Bug:**
-
-A clear description of what is the bug is.
-
-**To Reproduce:**
-1.
-2.
-3.
-
-**Expected Behavior:**
-
-A clear description of what you expected to happen.
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md
deleted file mode 100644
index 77310ae..0000000
--- a/.github/ISSUE_TEMPLATE/Feature_request.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-
----
-
-*Please consider making a Pull Request if you are capable of doing so.*
-
-**Description what you'd like to happen:**
-
-A clear description if the feature or behavior you'd like implemented.
-
-**Describe alternatives you've considered:**
-
-A clear description of any alternative solutions you've considered.
diff --git a/.github/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE.md
similarity index 78%
rename from .github/pull_request_template.md
rename to .github/PULL_REQUEST_TEMPLATE.md
index 6307e10..b4035a9 100644
--- a/.github/pull_request_template.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,8 +1,9 @@
+
### Guidelines
-1. You must run the `spotlessApply` task before committing, either through Android Studio or with `./gradlew spotlessApply`.
+1. You must run the `spotlessApply` task before commiting, either through Android Studio or with `./gradlew spotlessApply`.
2. A PR should be focused and contained. If you are changing multiple unrelated things, they should be in separate PRs.
3. A PR should fix a bug or solve a problem - something that only you would use is not necessarily something that should be published.
4. Give your PR a detailed title and description - look over your code one last time before actually creating the PR. Give it a self-review.
-**If you do not follow the guidelines, your PR will be rejected.**
+**If you do not follow the guidelines, your PR will be rejected.**
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 454e51a..161128f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -180,6 +180,4 @@ gradle-app.setting
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
-# gradle/wrapper/gradle-wrapper.properties
-
-app/google-services.json
\ No newline at end of file
+# gradle/wrapper/gradle-wrapper.properties
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 50f0406..320b3df 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -5,42 +5,7 @@
-
-
-
-
-
-
-
+
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..40a7b55
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,19 @@
+language: android
+jdk: oraclejdk8
+before_script:
+ - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a
+ - emulator -avd test -no-audio -no-window &
+ - android-wait-for-emulator
+ - adb shell input keyevent 82 &
+android:
+ components:
+ - tools
+ - platform-tools
+ - build-tools-28.0.3
+ - android-28
+ - extra-android-support
+ - extra-android-m2repository
+ - extra-google-m2repository
+
+ licenses:
+ - '.+'
diff --git a/README.md b/README.md
index 84ee16c..8e2ecb5 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,9 @@
## Nock Nock
+[](https://travis-ci.org/afollestad/nock-nock)
[](https://www.apache.org/licenses/LICENSE-2.0.html)
-
+
Nock Nock is a simple app which allows you to monitor your websites for maximum uptime.
diff --git a/app/build.gradle b/app/build.gradle
index d6b315e..7e04aaf 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,6 +4,18 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
+apply plugin: 'io.fabric'
+
+def getFabricApiKey() {
+ def propsFile = project.rootProject.file('local.properties')
+ if (!propsFile.exists()) {
+ return ""
+ }
+ Properties properties = new Properties()
+ properties.load(propsFile.newDataInputStream())
+ return properties.getProperty("fabric.apikey") ?: ""
+}
+
android {
compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools
@@ -14,15 +26,16 @@ android {
targetSdkVersion versions.compileSdk
versionCode versions.publishVersionCode
versionName versions.publishVersion
+ manifestPlaceholders = [fabricKey:getFabricApiKey()]
}
- compileOptions {
- sourceCompatibility 1.8
- targetCompatibility 1.8
- }
-
- packagingOptions {
- exclude 'META-INF/atomicfu.kotlin_module'
+ buildTypes {
+ debug {
+ buildConfigField "String", "FABRIC_API_KEY", "\"\""
+ }
+ release {
+ buildConfigField "String", "FABRIC_API_KEY", "\"${getFabricApiKey()}\""
+ }
}
}
@@ -38,7 +51,6 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:' + versions.androidxRecyclerView
implementation 'com.google.android.material:material:' + versions.googleMaterial
implementation 'androidx.browser:browser:' + versions.androidxBrowser
- implementation 'com.google.firebase:firebase-core:' + versions.firebaseCore
// Lifecycle
kapt 'androidx.lifecycle:lifecycle-compiler:' + versions.lifecycle
@@ -72,8 +84,4 @@ dependencies {
androidTestImplementation 'androidx.test:rules:' + versions.androidxTestRunner
}
-apply from: '../spotless.gradle'
-apply from: '../mock/mock.gradle'
-
-apply plugin: "io.fabric"
-apply plugin: 'com.google.gms.google-services'
\ No newline at end of file
+apply from: '../spotless.gradle'
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 332578e..b68a968 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -50,6 +50,9 @@
+
diff --git a/app/src/main/java/com/afollestad/nocknock/AppExt.kt b/app/src/main/java/com/afollestad/nocknock/AppExt.kt
index 828e01f..c7af855 100644
--- a/app/src/main/java/com/afollestad/nocknock/AppExt.kt
+++ b/app/src/main/java/com/afollestad/nocknock/AppExt.kt
@@ -20,12 +20,12 @@ import android.app.Application
import android.app.Application.ActivityLifecycleCallbacks
import android.content.ActivityNotFoundException
import android.content.Intent
+import android.net.Uri
import android.os.Bundle
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
import androidx.core.text.HtmlCompat.fromHtml
import com.afollestad.materialdialogs.utils.MDUtil.resolveColor
-import com.afollestad.nocknock.utilities.ext.toUri
import com.afollestad.nocknock.utilities.ui.toast
typealias ActivityLifeChange = (activity: Activity, resumed: Boolean) -> Unit
@@ -57,6 +57,8 @@ fun Application.onActivityLifeChange(cb: ActivityLifeChange) {
fun String.toHtml() = fromHtml(this, FROM_HTML_MODE_LEGACY)
+fun String.toUri() = Uri.parse(this)!!
+
fun Activity.viewUrl(url: String) {
val customTabsIntent = CustomTabsIntent.Builder()
.apply {
diff --git a/app/src/main/java/com/afollestad/nocknock/NockNockApp.kt b/app/src/main/java/com/afollestad/nocknock/NockNockApp.kt
index 3c29301..2cd0af3 100644
--- a/app/src/main/java/com/afollestad/nocknock/NockNockApp.kt
+++ b/app/src/main/java/com/afollestad/nocknock/NockNockApp.kt
@@ -47,8 +47,10 @@ class NockNockApp : Application() {
Timber.plant(DebugTree())
}
- Timber.plant(FabricTree())
- Fabric.with(this, Crashlytics())
+ if (BuildConfig.FABRIC_API_KEY.isNotEmpty()) {
+ Timber.plant(FabricTree())
+ Fabric.with(this, Crashlytics())
+ }
val modules = listOf(
prefModule,
diff --git a/app/src/main/java/com/afollestad/nocknock/adapter/TagAdapter.kt b/app/src/main/java/com/afollestad/nocknock/adapter/TagAdapter.kt
deleted file mode 100644
index 7bf22e5..0000000
--- a/app/src/main/java/com/afollestad/nocknock/adapter/TagAdapter.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * Designed and developed by Aidan Follestad (@afollestad)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.afollestad.nocknock.adapter
-
-import android.graphics.Color.WHITE
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.OnClickListener
-import android.view.ViewGroup
-import androidx.core.content.ContextCompat
-import androidx.recyclerview.widget.RecyclerView
-import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import com.afollestad.nocknock.R
-import com.afollestad.nocknock.adapter.TagAdapter.TagViewHolder
-import kotlinx.android.synthetic.main.list_item_tag.view.chip
-
-typealias TagsListener = (tags: List) -> Unit
-
-/** @author Aidan Follestad (@afollestad) */
-class TagAdapter(
- private val listener: TagsListener
-) : RecyclerView.Adapter() {
-
- private val tags = mutableListOf()
- private val checked = mutableListOf()
-
- fun set(tags: List) {
- this.tags.run {
- clear()
- addAll(tags)
- }
- notifyDataSetChanged()
- }
-
- fun toggleChecked(index: Int) {
- if (checked.contains(index)) {
- checked.remove(index)
- } else {
- checked.add(index)
- }
- notifyItemChanged(index)
- listener.invoke(getCheckedTags())
- }
-
- private fun getCheckedTags(): List {
- return mutableListOf().apply {
- checked.forEach { index -> add(tags[index]) }
- }
- }
-
- override fun onCreateViewHolder(
- parent: ViewGroup,
- viewType: Int
- ): TagViewHolder {
- val view = LayoutInflater.from(parent.context)
- .inflate(R.layout.list_item_tag, parent, false)
- return TagViewHolder(view, this)
- }
-
- override fun getItemCount() = tags.size
-
- override fun onBindViewHolder(
- holder: TagViewHolder,
- position: Int
- ) {
- holder.bind(tags[position], checked.contains(position))
- }
-
- /** @author Aidan Follestad (@afollestad) */
- class TagViewHolder(
- itemView: View,
- private val adapter: TagAdapter
- ) : ViewHolder(itemView), OnClickListener {
-
- override fun onClick(v: View) = adapter.toggleChecked(adapterPosition)
-
- init {
- itemView.setOnClickListener(this)
- }
-
- fun bind(
- name: String,
- checked: Boolean
- ) = itemView.chip.run {
- text = name
- setTextColor(
- if (checked) {
- WHITE
- } else {
- ContextCompat.getColor(itemView.context, R.color.unchecked_chip_text)
- }
- )
- setBackgroundResource(
- if (checked) {
- R.drawable.checked_chip_selector
- } else {
- R.drawable.unchecked_chip_selector
- }
- )
- }
- }
-}
diff --git a/app/src/main/java/com/afollestad/nocknock/dialogs/AboutDialog.kt b/app/src/main/java/com/afollestad/nocknock/dialogs/AboutDialog.kt
index f0c152f..7c8e8e4 100644
--- a/app/src/main/java/com/afollestad/nocknock/dialogs/AboutDialog.kt
+++ b/app/src/main/java/com/afollestad/nocknock/dialogs/AboutDialog.kt
@@ -20,7 +20,6 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.nocknock.BuildConfig
import com.afollestad.nocknock.R
/** @author Aidan Follestad (@afollestad) */
@@ -35,9 +34,8 @@ class AboutDialog : DialogFragment() {
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val context = activity ?: throw IllegalStateException("Oh no!")
- return MaterialDialog(context)
- .title(text = getString(R.string.app_name_x, BuildConfig.VERSION_NAME))
+ return MaterialDialog(activity!!)
+ .title(R.string.about)
.positiveButton(R.string.dismiss)
.message(R.string.about_body, html = true, lineHeightMultiplier = 1.4f)
}
diff --git a/app/src/main/java/com/afollestad/nocknock/koin/MainModule.kt b/app/src/main/java/com/afollestad/nocknock/koin/MainModule.kt
index 07f6410..ba6dc40 100644
--- a/app/src/main/java/com/afollestad/nocknock/koin/MainModule.kt
+++ b/app/src/main/java/com/afollestad/nocknock/koin/MainModule.kt
@@ -23,9 +23,6 @@ import android.content.Context.NOTIFICATION_SERVICE
import androidx.room.Room.databaseBuilder
import com.afollestad.nocknock.data.AppDatabase
import com.afollestad.nocknock.data.Database1to2Migration
-import com.afollestad.nocknock.data.Database2to3Migration
-import com.afollestad.nocknock.data.Database3to4Migration
-import com.afollestad.nocknock.data.Database4to5Migration
import com.afollestad.nocknock.notifications.Qualifiers.MAIN_ACTIVITY_CLASS
import com.afollestad.nocknock.ui.main.MainActivity
import com.afollestad.nocknock.utilities.ext.systemService
@@ -41,12 +38,7 @@ val mainModule = module {
single {
databaseBuilder(get(), AppDatabase::class.java, "NockNock.db")
- .addMigrations(
- Database1to2Migration(),
- Database2to3Migration(),
- Database3to4Migration(),
- Database4to5Migration()
- )
+ .addMigrations(Database1to2Migration())
.build()
}
diff --git a/app/src/main/java/com/afollestad/nocknock/ui/DarkModeSwitchActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/DarkModeSwitchActivity.kt
index 3220567..c43b729 100644
--- a/app/src/main/java/com/afollestad/nocknock/ui/DarkModeSwitchActivity.kt
+++ b/app/src/main/java/com/afollestad/nocknock/ui/DarkModeSwitchActivity.kt
@@ -15,15 +15,10 @@
*/
package com.afollestad.nocknock.ui
-import android.content.res.Configuration
-import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.afollestad.nocknock.R
import com.afollestad.nocknock.koin.PREF_DARK_MODE
-import com.afollestad.nocknock.ui.NightMode.DISABLED
-import com.afollestad.nocknock.ui.NightMode.ENABLED
-import com.afollestad.nocknock.ui.NightMode.UNKNOWN
import com.afollestad.nocknock.utilities.rx.attachLifecycle
import com.afollestad.rxkprefs.Pref
import org.koin.android.ext.android.inject
@@ -40,35 +35,16 @@ abstract class DarkModeSwitchActivity : AppCompatActivity() {
setTheme(themeRes())
super.onCreate(savedInstanceState)
- if (getCurrentNightMode() == UNKNOWN) {
- darkModePref.observe()
- .filter { it != isDarkModeEnabled }
- .subscribe {
- log("Theme changed, recreating Activity.")
- recreate()
- }
- .attachLifecycle(this)
- }
+ darkModePref.observe()
+ .filter { it != isDarkModeEnabled }
+ .subscribe {
+ log("Theme changed, recreating Activity.")
+ recreate()
+ }
+ .attachLifecycle(this)
}
- protected fun getCurrentNightMode(): NightMode {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
- return UNKNOWN
- }
- return when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
- Configuration.UI_MODE_NIGHT_YES -> return ENABLED
- Configuration.UI_MODE_NIGHT_NO -> return DISABLED
- else -> UNKNOWN
- }
- }
-
- protected fun isDarkMode(): Boolean {
- return when (getCurrentNightMode()) {
- ENABLED -> true
- DISABLED -> false
- else -> darkModePref.get()
- }
- }
+ protected fun isDarkMode() = darkModePref.get()
protected fun toggleDarkMode() = setDarkMode(!isDarkMode())
diff --git a/app/src/main/java/com/afollestad/nocknock/ui/NightMode.kt b/app/src/main/java/com/afollestad/nocknock/ui/NightMode.kt
deleted file mode 100644
index 2930fea..0000000
--- a/app/src/main/java/com/afollestad/nocknock/ui/NightMode.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * Designed and developed by Aidan Follestad (@afollestad)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.afollestad.nocknock.ui
-
-/** @author Aidan Follestad (@afollestad) */
-enum class NightMode {
- /** Night mode is on at the system level. */
- ENABLED,
- /** Night mode is off at the system level. */
- DISABLED,
- /** We don't know about night mode, fallback to custom impl. */
- UNKNOWN
-}
diff --git a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivity.kt
index e15a29f..e3e9855 100644
--- a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivity.kt
+++ b/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivity.kt
@@ -16,32 +16,18 @@
package com.afollestad.nocknock.ui.addsite
import android.annotation.SuppressLint
-import android.content.Intent
-import android.content.Intent.ACTION_OPEN_DOCUMENT
-import android.content.Intent.CATEGORY_OPENABLE
import android.os.Bundle
import android.widget.ArrayAdapter
-import androidx.lifecycle.Observer
import com.afollestad.nocknock.R
-import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.ValidationMode
import com.afollestad.nocknock.ui.DarkModeSwitchActivity
-import com.afollestad.nocknock.ui.viewsite.KEY_SITE
-import com.afollestad.nocknock.utilities.ext.onTextChanged
-import com.afollestad.nocknock.utilities.ext.setTextAndMaintainSelection
-import com.afollestad.nocknock.utilities.livedata.distinct
-import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
-import com.afollestad.nocknock.viewcomponents.ext.isVisibleCondition
-import com.afollestad.nocknock.viewcomponents.ext.onScroll
import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData
+import com.afollestad.nocknock.viewcomponents.livedata.toViewError
import com.afollestad.nocknock.viewcomponents.livedata.toViewText
import com.afollestad.nocknock.viewcomponents.livedata.toViewVisibility
-import com.afollestad.vvalidator.form
-import com.afollestad.vvalidator.form.Form
import kotlinx.android.synthetic.main.activity_addsite.checkIntervalLayout
-import kotlinx.android.synthetic.main.activity_addsite.headersLayout
+import kotlinx.android.synthetic.main.activity_addsite.doneBtn
import kotlinx.android.synthetic.main.activity_addsite.inputName
-import kotlinx.android.synthetic.main.activity_addsite.inputTags
import kotlinx.android.synthetic.main.activity_addsite.inputUrl
import kotlinx.android.synthetic.main.activity_addsite.loadingProgress
import kotlinx.android.synthetic.main.activity_addsite.responseTimeoutInput
@@ -49,54 +35,44 @@ import kotlinx.android.synthetic.main.activity_addsite.responseValidationMode
import kotlinx.android.synthetic.main.activity_addsite.responseValidationSearchTerm
import kotlinx.android.synthetic.main.activity_addsite.retryPolicyLayout
import kotlinx.android.synthetic.main.activity_addsite.scriptInputLayout
-import kotlinx.android.synthetic.main.activity_addsite.scrollView
-import kotlinx.android.synthetic.main.activity_addsite.sslCertificateBrowse
-import kotlinx.android.synthetic.main.activity_addsite.sslCertificateInput
import kotlinx.android.synthetic.main.activity_addsite.textUrlWarning
import kotlinx.android.synthetic.main.activity_addsite.validationModeDescription
import kotlinx.android.synthetic.main.include_app_bar.toolbar
import org.koin.androidx.viewmodel.ext.android.viewModel
-import kotlinx.android.synthetic.main.include_app_bar.app_toolbar as appToolbar
import kotlinx.android.synthetic.main.include_app_bar.toolbar_title as toolbarTitle
/** @author Aidan Follestad (@afollestad) */
class AddSiteActivity : DarkModeSwitchActivity() {
- companion object {
- private const val SELECT_CERT_FILE_RQ = 23
- }
private val viewModel by viewModel()
- private lateinit var validationForm: Form
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_addsite)
setupUi()
- setupValidation()
lifecycle.addObserver(viewModel)
- // Populate view model with initial data
- val model = intent.getSerializableExtra(KEY_SITE) as? Site
- model?.let { viewModel.prePopulateFromModel(model) }
-
// Loading
loadingProgress.observe(this, viewModel.onIsLoading())
// Name
inputName.attachLiveData(this, viewModel.name)
-
- // Tags
- inputTags.attachLiveData(this, viewModel.tags)
+ viewModel.onNameError()
+ .toViewError(this, inputName)
// Url
inputUrl.attachLiveData(this, viewModel.url)
+ viewModel.onUrlError()
+ .toViewError(this, inputUrl)
viewModel.onUrlWarningVisibility()
.toViewVisibility(this, textUrlWarning)
// Timeout
responseTimeoutInput.attachLiveData(this, viewModel.timeout)
+ viewModel.onTimeoutError()
+ .toViewError(this, responseTimeoutInput)
// Validation mode
responseValidationMode.attachLiveData(
@@ -105,6 +81,8 @@ class AddSiteActivity : DarkModeSwitchActivity() {
outTransformer = { ValidationMode.fromIndex(it) },
inTransformer = { it.toIndex() }
)
+ viewModel.onValidationSearchTermError()
+ .toViewError(this, responseValidationSearchTerm)
viewModel.onValidationModeDescription()
.toViewText(this, validationModeDescription)
@@ -117,19 +95,30 @@ class AddSiteActivity : DarkModeSwitchActivity() {
viewModel.onValidationSearchTermVisibility()
.toViewVisibility(this, responseValidationSearchTerm)
- // SSL certificate
- sslCertificateInput.onTextChanged { viewModel.certificateUri.value = it }
- viewModel.certificateUri.distinct()
- .observe(this, Observer { sslCertificateInput.setTextAndMaintainSelection(it) })
+ // Validation script
+ scriptInputLayout.attach(
+ codeData = viewModel.validationScript,
+ errorData = viewModel.onValidationScriptError(),
+ visibility = viewModel.onValidationScriptVisibility()
+ )
- // Headers
- headersLayout.attach(viewModel.headers)
+ // Check interval
+ checkIntervalLayout.attach(
+ valueData = viewModel.checkIntervalValue,
+ multiplierData = viewModel.checkIntervalUnit,
+ errorData = viewModel.onCheckIntervalError()
+ )
+
+ // Retry Policy
+ retryPolicyLayout.attach(
+ timesData = viewModel.retryPolicyTimes,
+ minutesData = viewModel.retryPolicyMinutes
+ )
}
private fun setupUi() {
toolbarTitle.setText(R.string.add_site)
toolbar.run {
- inflateMenu(R.menu.menu_addsite)
setNavigationIcon(R.drawable.ic_action_close)
setNavigationOnClickListener { finish() }
}
@@ -142,94 +131,12 @@ class AddSiteActivity : DarkModeSwitchActivity() {
validationOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown)
responseValidationMode.adapter = validationOptionsAdapter
- scrollView.onScroll {
- appToolbar.elevation = if (it > appToolbar.measuredHeight / 2) {
- appToolbar.dimenFloat(R.dimen.default_elevation)
- } else {
- 0f
+ // Done button
+ doneBtn.setOnClickListener {
+ viewModel.commit {
+ setResult(RESULT_OK)
+ finish()
}
}
-
- // SSL certificate
- sslCertificateBrowse.setOnClickListener {
- val intent = Intent(ACTION_OPEN_DOCUMENT).apply {
- addCategory(CATEGORY_OPENABLE)
- type = "*/*"
- }
- startActivityForResult(intent, SELECT_CERT_FILE_RQ)
- }
- }
-
- private fun setupValidation() {
- validationForm = form {
- input(inputName, name = "Name") {
- isNotEmpty().description(R.string.please_enter_name)
- }
- input(inputUrl, name = "URL") {
- isNotEmpty().description(R.string.please_enter_url)
- isUrl().description(R.string.please_enter_valid_url)
- }
- input(responseTimeoutInput, name = "Timeout", optional = true) {
- isNumber().greaterThan(0)
- .description(R.string.please_enter_networkTimeout)
- }
- input(responseValidationSearchTerm, name = "Search term") {
- conditional(responseValidationSearchTerm.isVisibleCondition()) {
- isNotEmpty().description(R.string.please_enter_search_term)
- }
- }
- input(sslCertificateInput, name = "Certificate Path", optional = true) {
- isUri().hasScheme("file", "content")
- .that { it.host != null }
- .description(R.string.please_enter_validCertUri)
- }
- submitWith(toolbar.menu, R.id.commit) {
- viewModel.commit {
- setResult(RESULT_OK)
- finish()
- }
- }
- }
-
- // Validation script
- scriptInputLayout.attach(
- codeData = viewModel.validationScript,
- visibility = viewModel.onValidationScriptVisibility(),
- form = validationForm
- )
-
- // Check interval
- checkIntervalLayout.attach(
- valueData = viewModel.checkIntervalValue,
- multiplierData = viewModel.checkIntervalUnit,
- form = validationForm
- )
-
- // Retry Policy
- retryPolicyLayout.attach(
- timesData = viewModel.retryPolicyTimes,
- minutesData = viewModel.retryPolicyMinutes,
- form = validationForm
- )
- }
-
- override fun onResume() {
- super.onResume()
- appToolbar.elevation = if (scrollView.scrollY > appToolbar.measuredHeight / 2) {
- appToolbar.dimenFloat(R.dimen.default_elevation)
- } else {
- 0f
- }
- }
-
- override fun onActivityResult(
- requestCode: Int,
- resultCode: Int,
- resultData: Intent?
- ) {
- super.onActivityResult(requestCode, resultCode, resultData)
- if (requestCode == SELECT_CERT_FILE_RQ && resultCode == RESULT_OK) {
- sslCertificateInput.setText(resultData?.data?.toString() ?: "")
- }
}
}
diff --git a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteViewModel.kt b/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteViewModel.kt
index d7d8ed5..ffc3619 100644
--- a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteViewModel.kt
+++ b/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteViewModel.kt
@@ -25,8 +25,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.OnLifecycleEvent
import com.afollestad.nocknock.R
import com.afollestad.nocknock.data.AppDatabase
-import com.afollestad.nocknock.data.model.Header
-import com.afollestad.nocknock.data.model.RetryPolicy
+import com.afollestad.nocknock.data.RetryPolicy
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.SiteSettings
import com.afollestad.nocknock.data.model.Status.WAITING
@@ -36,10 +35,11 @@ import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE
import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH
import com.afollestad.nocknock.data.model.ValidationResult
import com.afollestad.nocknock.data.putSite
-import com.afollestad.nocknock.engine.validation.ValidationExecutor
+import com.afollestad.nocknock.engine.validation.ValidationManager
import com.afollestad.nocknock.ui.ScopedViewModel
import com.afollestad.nocknock.utilities.ext.MINUTE
import com.afollestad.nocknock.utilities.livedata.map
+import com.afollestad.nocknock.viewcomponents.ext.isNullOrLessThan
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -49,14 +49,13 @@ import java.lang.System.currentTimeMillis
/** @author Aidan Follestad (@afollestad) */
class AddSiteViewModel(
private val database: AppDatabase,
- private val validationManager: ValidationExecutor,
+ private val validationManager: ValidationManager,
mainDispatcher: CoroutineDispatcher,
private val ioDispatcher: CoroutineDispatcher
) : ScopedViewModel(mainDispatcher), LifecycleObserver {
// Public properties
val name = MutableLiveData()
- val tags = MutableLiveData()
val url = MutableLiveData()
val timeout = MutableLiveData()
val validationMode = MutableLiveData()
@@ -66,8 +65,6 @@ class AddSiteViewModel(
val checkIntervalUnit = MutableLiveData()
val retryPolicyTimes = MutableLiveData()
val retryPolicyMinutes = MutableLiveData()
- val headers = MutableLiveData>()
- val certificateUri = MutableLiveData()
@OnLifecycleEvent(ON_START)
fun setDefaults() {
@@ -77,14 +74,24 @@ class AddSiteViewModel(
checkIntervalUnit.value = MINUTE
retryPolicyMinutes.value = 0
retryPolicyMinutes.value = 0
- tags.value = ""
- headers.value = emptyList()
}
+ // Private properties
private val isLoading = MutableLiveData()
+ private val nameError = MutableLiveData()
+ private val urlError = MutableLiveData()
+ private val timeoutError = MutableLiveData()
+ private val validationSearchTermError = MutableLiveData()
+ private val validationScriptError = MutableLiveData()
+ private val checkIntervalValueError = MutableLiveData()
+ // Expose private properties or calculated properties
@CheckResult fun onIsLoading(): LiveData = isLoading
+ @CheckResult fun onNameError(): LiveData = nameError
+
+ @CheckResult fun onUrlError(): LiveData = urlError
+
@CheckResult fun onUrlWarningVisibility(): LiveData {
return url.map {
val parsed = HttpUrl.parse(it)
@@ -92,6 +99,8 @@ class AddSiteViewModel(
}
}
+ @CheckResult fun onTimeoutError(): LiveData = timeoutError
+
@CheckResult fun onValidationModeDescription(): LiveData {
return validationMode.map {
when (it!!) {
@@ -102,9 +111,17 @@ class AddSiteViewModel(
}
}
- @CheckResult fun onValidationSearchTermVisibility() = validationMode.map { it == TERM_SEARCH }
+ @CheckResult fun onValidationSearchTermError(): LiveData = validationSearchTermError
- @CheckResult fun onValidationScriptVisibility() = validationMode.map { it == JAVASCRIPT }
+ @CheckResult fun onValidationSearchTermVisibility() =
+ validationMode.map { it == TERM_SEARCH }
+
+ @CheckResult fun onValidationScriptError(): LiveData = validationScriptError
+
+ @CheckResult fun onValidationScriptVisibility() =
+ validationMode.map { it == JAVASCRIPT }
+
+ @CheckResult fun onCheckIntervalError(): LiveData = checkIntervalValueError
// Actions
fun commit(done: () -> Unit) {
@@ -115,7 +132,7 @@ class AddSiteViewModel(
val storedModel = withContext(ioDispatcher) {
database.putSite(newModel)
}
- validationManager.scheduleValidation(
+ validationManager.scheduleCheck(
site = storedModel,
rightNow = true,
cancelPrevious = true
@@ -144,16 +161,75 @@ class AddSiteViewModel(
}
private fun generateDbModel(): Site? {
- val timeout = timeout.value ?: 10_000
- val cleanedTags = tags.value?.split(',')?.joinToString { it.trim() } ?: ""
+ var errorCount = 0
+
+ // Validation name
+ if (name.value.isNullOrEmpty()) {
+ nameError.value = R.string.please_enter_name
+ errorCount++
+ } else {
+ nameError.value = null
+ }
+
+ // Validate URL
+ when {
+ url.value.isNullOrEmpty() -> {
+ urlError.value = R.string.please_enter_url
+ errorCount++
+ }
+ HttpUrl.parse(url.value!!) == null -> {
+ urlError.value = R.string.please_enter_valid_url
+ errorCount++
+ }
+ else -> {
+ urlError.value = null
+ }
+ }
+
+ // Validate timeout
+ if (timeout.value.isNullOrLessThan(1)) {
+ timeoutError.value = R.string.please_enter_networkTimeout
+ errorCount++
+ } else {
+ timeoutError.value = null
+ }
+
+ // Validate check interval
+ if (checkIntervalValue.value.isNullOrLessThan(1)) {
+ checkIntervalValueError.value = R.string.please_enter_check_interval
+ errorCount++
+ } else {
+ checkIntervalValueError.value = null
+ }
+
+ // Validate arguments
+ if (validationMode.value == TERM_SEARCH &&
+ validationSearchTerm.value.isNullOrEmpty()
+ ) {
+ errorCount++
+ validationSearchTermError.value = R.string.please_enter_search_term
+ validationScriptError.value = null
+ } else if (validationMode.value == JAVASCRIPT &&
+ validationScript.value.isNullOrEmpty()
+ ) {
+ errorCount++
+ validationSearchTermError.value = null
+ validationScriptError.value = R.string.please_enter_javaScript
+ } else {
+ validationSearchTermError.value = null
+ validationScriptError.value = null
+ }
+
+ if (errorCount > 0) {
+ return null
+ }
val newSettings = SiteSettings(
validationIntervalMs = getCheckIntervalMs(),
validationMode = validationMode.value!!,
validationArgs = getValidationArgs(),
- networkTimeout = timeout,
- disabled = false,
- certificate = certificateUri.value?.toString()
+ networkTimeout = timeout.value!!,
+ disabled = false
)
val newLastResult = ValidationResult(
@@ -165,10 +241,7 @@ class AddSiteViewModel(
val retryPolicyTimes = retryPolicyTimes.value ?: 0
val retryPolicyMinutes = retryPolicyMinutes.value ?: 0
val newRetryPolicy: RetryPolicy? = if (retryPolicyTimes > 0 && retryPolicyMinutes > 0) {
- RetryPolicy(
- count = retryPolicyTimes,
- minutes = retryPolicyMinutes
- )
+ RetryPolicy(count = retryPolicyTimes, minutes = retryPolicyMinutes)
} else {
null
}
@@ -177,11 +250,9 @@ class AddSiteViewModel(
id = 0,
name = name.value!!.trim(),
url = url.value!!.trim(),
- tags = cleanedTags,
settings = newSettings,
lastResult = newLastResult,
- retryPolicy = newRetryPolicy,
- headers = headers.value ?: emptyList()
+ retryPolicy = newRetryPolicy
)
}
}
diff --git a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteViewModelExt.kt b/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteViewModelExt.kt
deleted file mode 100644
index c524555..0000000
--- a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteViewModelExt.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/**
- * Designed and developed by Aidan Follestad (@afollestad)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.afollestad.nocknock.ui.addsite
-
-import com.afollestad.nocknock.data.model.RetryPolicy
-import com.afollestad.nocknock.data.model.Site
-import com.afollestad.nocknock.data.model.ValidationMode.JAVASCRIPT
-import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH
-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 kotlin.math.ceil
-
-fun AddSiteViewModel.prePopulateFromModel(site: Site) {
- val settings = site.settings ?: throw IllegalArgumentException("Settings must be populated!")
-
- name.value = site.name
- tags.value = site.tags
- url.value = site.url
- timeout.value = settings.networkTimeout
-
- validationMode.value = settings.validationMode
- when (settings.validationMode) {
- TERM_SEARCH -> {
- validationSearchTerm.value = settings.validationArgs
- validationScript.value = null
- }
- JAVASCRIPT -> {
- validationSearchTerm.value = null
- validationScript.value = settings.validationArgs
- }
- else -> {
- validationSearchTerm.value = null
- validationScript.value = null
- }
- }
-
- setCheckInterval(settings.validationIntervalMs)
- setRetryPolicy(site.retryPolicy)
- headers.value = site.headers
-}
-
-private fun AddSiteViewModel.setCheckInterval(interval: Long) {
- when {
- interval >= WEEK -> {
- checkIntervalValue.value =
- getIntervalFromUnit(interval, WEEK)
- checkIntervalUnit.value = WEEK
- }
- interval >= DAY -> {
- checkIntervalValue.value =
- getIntervalFromUnit(interval, DAY)
- checkIntervalUnit.value = DAY
- }
- interval >= HOUR -> {
- checkIntervalValue.value =
- getIntervalFromUnit(interval, HOUR)
- checkIntervalUnit.value = HOUR
- }
- interval >= MINUTE -> {
- checkIntervalValue.value =
- getIntervalFromUnit(interval, MINUTE)
- checkIntervalUnit.value = MINUTE
- }
- else -> {
- checkIntervalValue.value = 0
- checkIntervalUnit.value = MINUTE
- }
- }
-}
-
-private fun AddSiteViewModel.setRetryPolicy(policy: RetryPolicy?) {
- if (policy == null) return
- retryPolicyTimes.value = policy.count
- retryPolicyMinutes.value = policy.minutes
-}
-
-private fun getIntervalFromUnit(
- millis: Long,
- unit: Long
-): Int {
- val intervalFloat = millis.toFloat()
- val byFloat = unit.toFloat()
- return ceil(intervalFloat / byFloat).toInt()
-}
diff --git a/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivity.kt
index aec76d8..eba93e4 100644
--- a/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivity.kt
+++ b/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivity.kt
@@ -21,19 +21,20 @@ import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItems
+import com.afollestad.materialdialogs.list.listItemsSingleChoice
import com.afollestad.nocknock.R
import com.afollestad.nocknock.adapter.SiteAdapter
-import com.afollestad.nocknock.adapter.TagAdapter
import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.dialogs.AboutDialog
import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.ui.DarkModeSwitchActivity
-import com.afollestad.nocknock.ui.NightMode.UNKNOWN
import com.afollestad.nocknock.utilities.providers.IntentProvider
+import com.afollestad.nocknock.utilities.ui.toast
+import com.afollestad.nocknock.viewUrl
+import com.afollestad.nocknock.viewUrlWithApp
import com.afollestad.nocknock.viewcomponents.livedata.toViewVisibility
import kotlinx.android.synthetic.main.activity_main.fab
import kotlinx.android.synthetic.main.activity_main.list
@@ -42,7 +43,6 @@ import kotlinx.android.synthetic.main.include_app_bar.toolbar
import kotlinx.android.synthetic.main.include_empty_view.emptyText
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
-import kotlinx.android.synthetic.main.activity_main.tags_list as tagsList
/** @author Aidan Follestad (@afollestad) */
class MainActivity : DarkModeSwitchActivity() {
@@ -53,7 +53,6 @@ class MainActivity : DarkModeSwitchActivity() {
internal val viewModel by viewModel()
private lateinit var siteAdapter: SiteAdapter
- private lateinit var tagAdapter: TagAdapter
private val statusUpdateReceiver by lazy {
StatusUpdateIntentReceiver(application, intentProvider) {
@@ -77,10 +76,6 @@ class MainActivity : DarkModeSwitchActivity() {
.observe(this, Observer { siteAdapter.set(it) })
viewModel.onEmptyTextVisibility()
.toViewVisibility(this, emptyText)
- viewModel.onTags()
- .observe(this, Observer { tagAdapter.set(it) })
- viewModel.onTagsListVisibility()
- .toViewVisibility(this, tagsList)
loadingProgress.observe(this, viewModel.onIsLoading())
processIntent(intent)
@@ -90,35 +85,24 @@ class MainActivity : DarkModeSwitchActivity() {
toolbar.run {
inflateMenu(R.menu.menu_main)
menu.findItem(R.id.dark_mode)
- .apply {
- if (getCurrentNightMode() == UNKNOWN) {
- isChecked = isDarkMode()
- } else {
- isVisible = false
- }
- }
+ .isChecked = isDarkMode()
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.about -> AboutDialog.show(this@MainActivity)
R.id.dark_mode -> toggleDarkMode()
+ R.id.support_me -> supportMe()
}
return@setOnMenuItemClickListener true
}
}
siteAdapter = SiteAdapter(this::onSiteSelected)
+
list.run {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = siteAdapter
addItemDecoration(DividerItemDecoration(this@MainActivity, VERTICAL))
}
-
- tagAdapter = TagAdapter(viewModel::onTagSelection)
- tagsList.run {
- layoutManager = LinearLayoutManager(this@MainActivity, HORIZONTAL, false)
- adapter = tagAdapter
- }
-
fab.setOnClickListener { addSite() }
}
@@ -137,8 +121,7 @@ class MainActivity : DarkModeSwitchActivity() {
listItems(R.array.site_long_options) { _, i, _ ->
when (i) {
0 -> viewModel.refreshSite(model)
- 1 -> addSiteForDuplication(model)
- 2 -> maybeRemoveSite(model)
+ 1 -> maybeRemoveSite(model)
}
}
}
@@ -146,4 +129,20 @@ class MainActivity : DarkModeSwitchActivity() {
viewSite(model)
}
}
+
+ private fun supportMe() {
+ MaterialDialog(this).show {
+ title(R.string.support_me)
+ message(R.string.support_me_message, html = true, lineHeightMultiplier = 1.4f)
+ listItemsSingleChoice(R.array.donation_options) { _, index, _ ->
+ when (index) {
+ 0 -> viewUrl("https://paypal.me/AidanFollestad")
+ 1 -> viewUrlWithApp("https://cash.me/\$afollestad", pkg = "com.squareup.cash")
+ 2 -> viewUrlWithApp("https://venmo.com/afollestad", pkg = "com.venmo")
+ }
+ toast(R.string.thank_you)
+ }
+ positiveButton(R.string.next)
+ }
+ }
}
diff --git a/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivityExt.kt b/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivityExt.kt
index e11ca08..422cdc5 100644
--- a/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivityExt.kt
+++ b/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivityExt.kt
@@ -28,23 +28,10 @@ import com.afollestad.nocknock.utilities.providers.RealIntentProvider.Companion.
internal const val VIEW_SITE_RQ = 6923
internal const val ADD_SITE_RQ = 6969
-// ADD
-
internal fun MainActivity.addSite() {
- startActivityForResult(intentToAdd(), ADD_SITE_RQ)
+ startActivityForResult(Intent(this, AddSiteActivity::class.java), ADD_SITE_RQ)
}
-internal fun MainActivity.addSiteForDuplication(site: Site) {
- startActivityForResult(intentToAdd(site), ADD_SITE_RQ)
-}
-
-private fun MainActivity.intentToAdd(model: Site? = null) =
- Intent(this, AddSiteActivity::class.java).apply {
- model?.let { putExtra(KEY_SITE, it) }
- }
-
-// VIEW
-
internal fun MainActivity.viewSite(model: Site) {
startActivityForResult(intentToView(model), VIEW_SITE_RQ)
}
@@ -54,8 +41,6 @@ private fun MainActivity.intentToView(model: Site) =
putExtra(KEY_SITE, model)
}
-// MISC
-
internal fun MainActivity.maybeRemoveSite(model: Site) {
MaterialDialog(this).show {
title(R.string.remove_site)
diff --git a/app/src/main/java/com/afollestad/nocknock/ui/main/MainViewModel.kt b/app/src/main/java/com/afollestad/nocknock/ui/main/MainViewModel.kt
index bad64fc..bfb9bb9 100644
--- a/app/src/main/java/com/afollestad/nocknock/ui/main/MainViewModel.kt
+++ b/app/src/main/java/com/afollestad/nocknock/ui/main/MainViewModel.kt
@@ -25,7 +25,7 @@ import com.afollestad.nocknock.data.AppDatabase
import com.afollestad.nocknock.data.allSites
import com.afollestad.nocknock.data.deleteSite
import com.afollestad.nocknock.data.model.Site
-import com.afollestad.nocknock.engine.validation.ValidationExecutor
+import com.afollestad.nocknock.engine.validation.ValidationManager
import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.ui.ScopedViewModel
import kotlinx.coroutines.CoroutineDispatcher
@@ -36,7 +36,7 @@ import kotlinx.coroutines.withContext
class MainViewModel(
private val database: AppDatabase,
private val notificationManager: NockNotificationManager,
- private val validationManager: ValidationExecutor,
+ private val validationManager: ValidationManager,
mainDispatcher: CoroutineDispatcher,
private val ioDispatcher: CoroutineDispatcher
) : ScopedViewModel(mainDispatcher), LifecycleObserver {
@@ -44,8 +44,6 @@ class MainViewModel(
private val sites = MutableLiveData>()
private val isLoading = MutableLiveData()
private val emptyTextVisibility = MutableLiveData()
- private val tags = MutableLiveData>()
- private val tagsListVisibility = MutableLiveData()
@CheckResult fun onSites(): LiveData> = sites
@@ -53,14 +51,8 @@ class MainViewModel(
@CheckResult fun onEmptyTextVisibility(): LiveData = emptyTextVisibility
- @CheckResult fun onTags(): LiveData> = tags
-
- @CheckResult fun onTagsListVisibility(): LiveData = tagsListVisibility
-
@OnLifecycleEvent(ON_RESUME)
- fun onResume() = loadSites(emptyList())
-
- fun onTagSelection(tags: List) = loadSites(tags)
+ fun onResume() = loadSites()
fun postSiteUpdate(model: Site) {
val currentSites = sites.value ?: return
@@ -73,7 +65,7 @@ class MainViewModel(
}
fun refreshSite(model: Site) {
- validationManager.scheduleValidation(
+ validationManager.scheduleCheck(
site = model,
rightNow = true,
cancelPrevious = true
@@ -81,7 +73,7 @@ class MainViewModel(
}
fun removeSite(model: Site) {
- validationManager.cancelScheduledValidation(model)
+ validationManager.cancelCheck(model)
notificationManager.cancelStatusNotification(model)
scope.launch {
@@ -102,56 +94,27 @@ class MainViewModel(
}
}
- private fun loadSites(forTags: List) {
+ private fun loadSites() {
scope.launch {
notificationManager.cancelStatusNotifications()
+ sites.value = listOf()
emptyTextVisibility.value = false
isLoading.value = true
- val unfiltered = withContext(ioDispatcher) {
+ val result = withContext(ioDispatcher) {
database.allSites()
}
- var result = unfiltered
-
- if (forTags.isNotEmpty()) {
- result = result.filter { site ->
- val itemTags = site.tags.toLowerCase()
- .split(",")
- itemTags.any { tag -> forTags.contains(tag) }
- }
- }
sites.value = result
ensureCheckJobs()
isLoading.value = false
emptyTextVisibility.value = result.isEmpty()
-
- val tagsValues = pullOutTags(unfiltered)
- tags.value = tagsValues
- tagsListVisibility.value = tagsValues.isNotEmpty()
}
}
private suspend fun ensureCheckJobs() {
withContext(ioDispatcher) {
- validationManager.ensureScheduledValidations()
- }
- }
-
- private fun pullOutTags(sites: List): List {
- return mutableListOf().apply {
- for (site in sites) {
- val splitTags = site.tags.toLowerCase()
- .split(',')
- splitTags
- .filter { it.isNotEmpty() }
- .forEach { tag ->
- if (!this.contains(tag)) {
- this.add(tag)
- }
- }
- }
- sort()
+ validationManager.ensureScheduledChecks()
}
}
}
diff --git a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt
index 2aa312c..a193c6f 100644
--- a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt
+++ b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt
@@ -17,8 +17,6 @@ package com.afollestad.nocknock.ui.viewsite
import android.annotation.SuppressLint
import android.content.Intent
-import android.content.Intent.ACTION_OPEN_DOCUMENT
-import android.content.Intent.CATEGORY_OPENABLE
import android.os.Bundle
import android.widget.ArrayAdapter
import androidx.lifecycle.Observer
@@ -27,23 +25,18 @@ import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.ValidationMode
import com.afollestad.nocknock.ui.DarkModeSwitchActivity
-import com.afollestad.nocknock.utilities.ext.onTextChanged
-import com.afollestad.nocknock.utilities.ext.setTextAndMaintainSelection
-import com.afollestad.nocknock.utilities.livedata.distinct
import com.afollestad.nocknock.utilities.providers.IntentProvider
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
-import com.afollestad.nocknock.viewcomponents.ext.isVisibleCondition
import com.afollestad.nocknock.viewcomponents.ext.onScroll
import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData
+import com.afollestad.nocknock.viewcomponents.livedata.toViewError
import com.afollestad.nocknock.viewcomponents.livedata.toViewText
import com.afollestad.nocknock.viewcomponents.livedata.toViewVisibility
-import com.afollestad.vvalidator.form
-import com.afollestad.vvalidator.form.Form
import kotlinx.android.synthetic.main.activity_viewsite.checkIntervalLayout
-import kotlinx.android.synthetic.main.activity_viewsite.headersLayout
+import kotlinx.android.synthetic.main.activity_viewsite.disableChecksButton
+import kotlinx.android.synthetic.main.activity_viewsite.doneBtn
import kotlinx.android.synthetic.main.activity_viewsite.iconStatus
import kotlinx.android.synthetic.main.activity_viewsite.inputName
-import kotlinx.android.synthetic.main.activity_viewsite.inputTags
import kotlinx.android.synthetic.main.activity_viewsite.inputUrl
import kotlinx.android.synthetic.main.activity_viewsite.loadingProgress
import kotlinx.android.synthetic.main.activity_viewsite.responseTimeoutInput
@@ -52,8 +45,6 @@ import kotlinx.android.synthetic.main.activity_viewsite.responseValidationSearch
import kotlinx.android.synthetic.main.activity_viewsite.retryPolicyLayout
import kotlinx.android.synthetic.main.activity_viewsite.scriptInputLayout
import kotlinx.android.synthetic.main.activity_viewsite.scrollView
-import kotlinx.android.synthetic.main.activity_viewsite.sslCertificateBrowse
-import kotlinx.android.synthetic.main.activity_viewsite.sslCertificateInput
import kotlinx.android.synthetic.main.activity_viewsite.textLastCheckResult
import kotlinx.android.synthetic.main.activity_viewsite.textNextCheck
import kotlinx.android.synthetic.main.activity_viewsite.textUrlWarning
@@ -61,17 +52,12 @@ import kotlinx.android.synthetic.main.activity_viewsite.validationModeDescriptio
import kotlinx.android.synthetic.main.include_app_bar.toolbar
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
-import kotlinx.android.synthetic.main.include_app_bar.app_toolbar as appToolbar
import kotlinx.android.synthetic.main.include_app_bar.toolbar_title as toolbarTitle
/** @author Aidan Follestad (@afollestad) */
class ViewSiteActivity : DarkModeSwitchActivity() {
- companion object {
- private const val SELECT_CERT_FILE_RQ = 23
- }
internal val viewModel by viewModel()
- private lateinit var validationForm: Form
private val intentProvider by inject()
private val statusUpdateReceiver by lazy {
@@ -84,18 +70,17 @@ class ViewSiteActivity : DarkModeSwitchActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_viewsite)
-
- // Populate view model with initial data
- val model = intent.getSerializableExtra(KEY_SITE) as Site
- viewModel.setModel(model)
-
setupUi()
- setupValidation()
+
lifecycle.run {
addObserver(viewModel)
addObserver(statusUpdateReceiver)
}
+ // Populate view model with initial data
+ val model = intent.getSerializableExtra(KEY_SITE) as Site
+ viewModel.setModel(model)
+
// Loading
loadingProgress.observe(this, viewModel.onIsLoading())
@@ -107,17 +92,20 @@ class ViewSiteActivity : DarkModeSwitchActivity() {
// Name
inputName.attachLiveData(this, viewModel.name)
-
- // Tags
- inputTags.attachLiveData(this, viewModel.tags)
+ viewModel.onNameError()
+ .toViewError(this, inputName)
// Url
inputUrl.attachLiveData(this, viewModel.url)
+ viewModel.onUrlError()
+ .toViewError(this, inputUrl)
viewModel.onUrlWarningVisibility()
.toViewVisibility(this, textUrlWarning)
// Timeout
responseTimeoutInput.attachLiveData(this, viewModel.timeout)
+ viewModel.onTimeoutError()
+ .toViewError(this, responseTimeoutInput)
// Validation mode
responseValidationMode.attachLiveData(
@@ -126,6 +114,8 @@ class ViewSiteActivity : DarkModeSwitchActivity() {
outTransformer = { ValidationMode.fromIndex(it) },
inTransformer = { it.toIndex() }
)
+ viewModel.onValidationSearchTermError()
+ .toViewError(this, responseValidationSearchTerm)
viewModel.onValidationModeDescription()
.toViewText(this, validationModeDescription)
@@ -134,13 +124,25 @@ class ViewSiteActivity : DarkModeSwitchActivity() {
viewModel.onValidationSearchTermVisibility()
.toViewVisibility(this, responseValidationSearchTerm)
- // SSL certificate
- sslCertificateInput.onTextChanged { viewModel.certificateUri.value = it }
- viewModel.certificateUri.distinct()
- .observe(this, Observer { sslCertificateInput.setTextAndMaintainSelection(it) })
+ // Validation script
+ scriptInputLayout.attach(
+ codeData = viewModel.validationScript,
+ errorData = viewModel.onValidationScriptError(),
+ visibility = viewModel.onValidationScriptVisibility()
+ )
- // Headers
- headersLayout.attach(viewModel.headers)
+ // Check interval
+ checkIntervalLayout.attach(
+ valueData = viewModel.checkIntervalValue,
+ multiplierData = viewModel.checkIntervalUnit,
+ errorData = viewModel.onCheckIntervalError()
+ )
+
+ // Retry Policy
+ retryPolicyLayout.attach(
+ timesData = viewModel.retryPolicyTimes,
+ minutesData = viewModel.retryPolicyMinutes
+ )
// Last/next check
viewModel.onLastCheckResultText()
@@ -150,30 +152,25 @@ class ViewSiteActivity : DarkModeSwitchActivity() {
}
private fun setupUi() {
- toolbarTitle.text = ""
+ toolbarTitle.setText(R.string.view_site)
toolbar.run {
setNavigationIcon(R.drawable.ic_action_close)
setNavigationOnClickListener { finish() }
inflateMenu(R.menu.menu_viewsite)
-
menu.findItem(R.id.refresh)
.setActionView(R.layout.menu_item_refresh_icon)
.apply {
actionView.setOnClickListener { viewModel.checkNow() }
}
-
setOnMenuItemClickListener {
- when (it.itemId) {
- R.id.remove -> maybeRemoveSite()
- R.id.disableChecks -> maybeDisableChecks()
- }
+ maybeRemoveSite()
true
}
}
scrollView.onScroll {
- appToolbar.elevation = if (it > appToolbar.measuredHeight / 2) {
- appToolbar.dimenFloat(R.dimen.default_elevation)
+ toolbar.elevation = if (it > toolbar.height / 4) {
+ toolbar.dimenFloat(R.dimen.default_elevation)
} else {
0f
}
@@ -189,95 +186,14 @@ class ViewSiteActivity : DarkModeSwitchActivity() {
// Disabled button
viewModel.onDisableChecksVisibility()
- .observe(this, Observer {
- toolbar.menu.findItem(R.id.disableChecks)
- .isVisible = it
- })
+ .toViewVisibility(this, disableChecksButton)
+ disableChecksButton.setOnClickListener { maybeDisableChecks() }
- // Done item text
+ // Done button
viewModel.onDoneButtonText()
- .observe(this, Observer {
- toolbar.menu.findItem(R.id.commit)
- .setTitle(it)
- })
-
- // SSL certificate
- sslCertificateBrowse.setOnClickListener {
- val intent = Intent(ACTION_OPEN_DOCUMENT).apply {
- addCategory(CATEGORY_OPENABLE)
- type = "*/*"
- }
- startActivityForResult(intent, SELECT_CERT_FILE_RQ)
- }
- }
-
- private fun setupValidation() {
- validationForm = form {
- input(inputName, name = "Name") {
- isNotEmpty().description(R.string.please_enter_name)
- }
- input(inputUrl, name = "URL") {
- isNotEmpty().description(R.string.please_enter_url)
- isUrl().description(R.string.please_enter_valid_url)
- }
- input(responseValidationSearchTerm, name = "Search term") {
- conditional(responseValidationSearchTerm.isVisibleCondition()) {
- isNotEmpty().description(R.string.please_enter_search_term)
- }
- }
- input(responseTimeoutInput, name = "Timeout", optional = true) {
- isNumber().greaterThan(0)
- .description(R.string.please_enter_networkTimeout)
- }
- input(sslCertificateInput, name = "Certificate Path", optional = true) {
- isUri().hasScheme("file", "content")
- .that { it.host != null }
- .description(R.string.please_enter_validCertUri)
- }
- submitWith(toolbar.menu, R.id.commit) {
- viewModel.commit { finish() }
- }
- }
-
- // Validation script
- scriptInputLayout.attach(
- codeData = viewModel.validationScript,
- visibility = viewModel.onValidationScriptVisibility(),
- form = validationForm
- )
-
- // Check interval
- checkIntervalLayout.attach(
- valueData = viewModel.checkIntervalValue,
- multiplierData = viewModel.checkIntervalUnit,
- form = validationForm
- )
-
- // Retry Policy
- retryPolicyLayout.attach(
- timesData = viewModel.retryPolicyTimes,
- minutesData = viewModel.retryPolicyMinutes,
- form = validationForm
- )
- }
-
- override fun onResume() {
- super.onResume()
- appToolbar.elevation = if (scrollView.scrollY > appToolbar.measuredHeight / 2) {
- appToolbar.dimenFloat(R.dimen.default_elevation)
- } else {
- 0f
- }
- }
-
- override fun onActivityResult(
- requestCode: Int,
- resultCode: Int,
- resultData: Intent?
- ) {
- super.onActivityResult(requestCode, resultCode, resultData)
- if (requestCode == SELECT_CERT_FILE_RQ && resultCode == RESULT_OK) {
- sslCertificateInput.setText(resultData?.data?.toString() ?: "")
+ .toViewText(this, doneBtn)
+ doneBtn.setOnClickListener {
+ viewModel.commit { finish() }
}
}
diff --git a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModel.kt b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModel.kt
index b5c9f93..536eadd 100644
--- a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModel.kt
+++ b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModel.kt
@@ -23,9 +23,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.afollestad.nocknock.R
import com.afollestad.nocknock.data.AppDatabase
+import com.afollestad.nocknock.data.RetryPolicy
import com.afollestad.nocknock.data.deleteSite
-import com.afollestad.nocknock.data.model.Header
-import com.afollestad.nocknock.data.model.RetryPolicy
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.Status
import com.afollestad.nocknock.data.model.Status.WAITING
@@ -36,13 +35,14 @@ import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH
import com.afollestad.nocknock.data.model.ValidationResult
import com.afollestad.nocknock.data.model.textRes
import com.afollestad.nocknock.data.updateSite
-import com.afollestad.nocknock.engine.validation.ValidationExecutor
+import com.afollestad.nocknock.engine.validation.ValidationManager
import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.ui.ScopedViewModel
import com.afollestad.nocknock.utilities.ext.formatDate
import com.afollestad.nocknock.utilities.livedata.map
import com.afollestad.nocknock.utilities.livedata.zip
import com.afollestad.nocknock.utilities.providers.StringProvider
+import com.afollestad.nocknock.viewcomponents.ext.isNullOrLessThan
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -54,7 +54,7 @@ class ViewSiteViewModel(
private val stringProvider: StringProvider,
private val database: AppDatabase,
private val notificationManager: NockNotificationManager,
- private val validationManager: ValidationExecutor,
+ private val validationManager: ValidationManager,
mainDispatcher: CoroutineDispatcher,
private val ioDispatcher: CoroutineDispatcher
) : ScopedViewModel(mainDispatcher), LifecycleObserver {
@@ -64,7 +64,6 @@ class ViewSiteViewModel(
// Public properties
val status = MutableLiveData()
val name = MutableLiveData()
- val tags = MutableLiveData()
val url = MutableLiveData()
val timeout = MutableLiveData()
val validationMode = MutableLiveData()
@@ -74,15 +73,25 @@ class ViewSiteViewModel(
val checkIntervalUnit = MutableLiveData()
val retryPolicyTimes = MutableLiveData()
val retryPolicyMinutes = MutableLiveData()
- val headers = MutableLiveData>()
- val certificateUri = MutableLiveData()
internal val disabled = MutableLiveData()
internal val lastResult = MutableLiveData()
+ // Private properties
private val isLoading = MutableLiveData()
+ private val nameError = MutableLiveData()
+ private val urlError = MutableLiveData()
+ private val timeoutError = MutableLiveData()
+ private val validationSearchTermError = MutableLiveData()
+ private val validationScriptError = MutableLiveData()
+ private val checkIntervalValueError = MutableLiveData()
+ // Expose private properties or calculated properties
@CheckResult fun onIsLoading(): LiveData = isLoading
+ @CheckResult fun onNameError(): LiveData = nameError
+
+ @CheckResult fun onUrlError(): LiveData = urlError
+
@CheckResult fun onUrlWarningVisibility(): LiveData {
return url.map {
val parsed = HttpUrl.parse(it)
@@ -90,6 +99,8 @@ class ViewSiteViewModel(
}
}
+ @CheckResult fun onTimeoutError(): LiveData = timeoutError
+
@CheckResult fun onValidationModeDescription(): LiveData {
return validationMode.map {
when (it!!) {
@@ -100,11 +111,20 @@ class ViewSiteViewModel(
}
}
- @CheckResult fun onValidationSearchTermVisibility() = validationMode.map { it == TERM_SEARCH }
+ @CheckResult fun onValidationSearchTermError(): LiveData = validationSearchTermError
- @CheckResult fun onValidationScriptVisibility() = validationMode.map { it == JAVASCRIPT }
+ @CheckResult fun onValidationSearchTermVisibility() =
+ validationMode.map { it == TERM_SEARCH }
- @CheckResult fun onDisableChecksVisibility(): LiveData = disabled.map { !it }
+ @CheckResult fun onValidationScriptError(): LiveData = validationScriptError
+
+ @CheckResult fun onValidationScriptVisibility() =
+ validationMode.map { it == JAVASCRIPT }
+
+ @CheckResult fun onCheckIntervalError(): LiveData = checkIntervalValueError
+
+ @CheckResult fun onDisableChecksVisibility(): LiveData =
+ disabled.map { !it }
@CheckResult fun onDoneButtonText(): LiveData =
disabled.map {
@@ -148,7 +168,7 @@ class ViewSiteViewModel(
withContext(ioDispatcher) {
database.updateSite(updatedModel)
}
- validationManager.scheduleValidation(
+ validationManager.scheduleCheck(
site = updatedModel,
rightNow = true,
cancelPrevious = true
@@ -164,7 +184,7 @@ class ViewSiteViewModel(
status = WAITING
)
setModel(checkModel)
- validationManager.scheduleValidation(
+ validationManager.scheduleCheck(
site = checkModel,
rightNow = true,
cancelPrevious = true
@@ -172,7 +192,7 @@ class ViewSiteViewModel(
}
fun removeSite(done: () -> Unit) {
- validationManager.cancelScheduledValidation(site)
+ validationManager.cancelCheck(site)
notificationManager.cancelStatusNotification(site)
scope.launch {
@@ -186,7 +206,7 @@ class ViewSiteViewModel(
}
fun disableSite() {
- validationManager.cancelScheduledValidation(site)
+ validationManager.cancelCheck(site)
notificationManager.cancelStatusNotification(site)
scope.launch {
@@ -222,16 +242,75 @@ class ViewSiteViewModel(
}
private fun getUpdatedDbModel(): Site? {
- val timeout = timeout.value ?: 10_000
- val cleanedTags = tags.value?.split(',')?.joinToString(separator = ",") ?: ""
+ var errorCount = 0
+
+ // Validation name
+ if (name.value.isNullOrEmpty()) {
+ nameError.value = R.string.please_enter_name
+ errorCount++
+ } else {
+ nameError.value = null
+ }
+
+ // Validate URL
+ when {
+ url.value.isNullOrEmpty() -> {
+ urlError.value = R.string.please_enter_url
+ errorCount++
+ }
+ HttpUrl.parse(url.value!!) == null -> {
+ urlError.value = R.string.please_enter_valid_url
+ errorCount++
+ }
+ else -> {
+ urlError.value = null
+ }
+ }
+
+ // Validate timeout
+ if (timeout.value.isNullOrLessThan(1)) {
+ timeoutError.value = R.string.please_enter_networkTimeout
+ errorCount++
+ } else {
+ timeoutError.value = null
+ }
+
+ // Validate check interval
+ if (checkIntervalValue.value.isNullOrLessThan(1)) {
+ checkIntervalValueError.value = R.string.please_enter_check_interval
+ errorCount++
+ } else {
+ checkIntervalValueError.value = null
+ }
+
+ // Validate arguments
+ if (validationMode.value == TERM_SEARCH &&
+ validationSearchTerm.value.isNullOrEmpty()
+ ) {
+ errorCount++
+ validationSearchTermError.value = R.string.please_enter_search_term
+ validationScriptError.value = null
+ } else if (validationMode.value == JAVASCRIPT &&
+ validationScript.value.isNullOrEmpty()
+ ) {
+ errorCount++
+ validationSearchTermError.value = null
+ validationScriptError.value = R.string.please_enter_javaScript
+ } else {
+ validationSearchTermError.value = null
+ validationScriptError.value = null
+ }
+
+ if (errorCount > 0) {
+ return null
+ }
val newSettings = site.settings!!.copy(
validationIntervalMs = getCheckIntervalMs(),
validationMode = validationMode.value!!,
validationArgs = getValidationArgs(),
- networkTimeout = timeout,
- disabled = false,
- certificate = certificateUri.value?.toString()
+ networkTimeout = timeout.value!!,
+ disabled = false
)
val retryPolicyTimes = retryPolicyTimes.value ?: 0
@@ -239,16 +318,10 @@ class ViewSiteViewModel(
val retryPolicy: RetryPolicy? = if (retryPolicyTimes > 0 && retryPolicyMinutes > 0) {
if (site.retryPolicy != null) {
// Have existing policy, update it
- site.retryPolicy!!.copy(
- count = retryPolicyTimes,
- minutes = retryPolicyMinutes
- )
+ site.retryPolicy!!.copy(count = retryPolicyTimes, minutes = retryPolicyMinutes)
} else {
// Create new policy
- RetryPolicy(
- count = retryPolicyTimes,
- minutes = retryPolicyMinutes
- )
+ RetryPolicy(count = retryPolicyTimes, minutes = retryPolicyMinutes)
}
} else {
// No policy
@@ -257,11 +330,9 @@ class ViewSiteViewModel(
return site.copy(
name = name.value!!.trim(),
- tags = cleanedTags,
url = url.value!!.trim(),
settings = newSettings,
- retryPolicy = retryPolicy,
- headers = headers.value ?: emptyList()
+ retryPolicy = retryPolicy
)
.withStatus(status = WAITING)
}
diff --git a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModelExt.kt b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModelExt.kt
index 800f235..d13341a 100644
--- a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModelExt.kt
+++ b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModelExt.kt
@@ -15,7 +15,7 @@
*/
package com.afollestad.nocknock.ui.viewsite
-import com.afollestad.nocknock.data.model.RetryPolicy
+import com.afollestad.nocknock.data.RetryPolicy
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.Status.WAITING
import com.afollestad.nocknock.data.model.ValidationMode.JAVASCRIPT
@@ -32,7 +32,6 @@ fun ViewSiteViewModel.setModel(site: Site) {
status.value = site.lastResult?.status ?: WAITING
name.value = site.name
- tags.value = site.tags
url.value = site.url
timeout.value = settings.networkTimeout
@@ -54,12 +53,6 @@ fun ViewSiteViewModel.setModel(site: Site) {
setCheckInterval(settings.validationIntervalMs)
setRetryPolicy(site.retryPolicy)
- headers.value = site.headers
- if (settings.certificate == "null") {
- certificateUri.value = ""
- } else {
- certificateUri.value = settings.certificate
- }
this.disabled.value = settings.disabled
this.lastResult.value = site.lastResult
@@ -69,22 +62,22 @@ private fun ViewSiteViewModel.setCheckInterval(interval: Long) {
when {
interval >= WEEK -> {
checkIntervalValue.value =
- getIntervalFromUnit(interval, WEEK)
+ getIntervalFromUnit(interval, WEEK)
checkIntervalUnit.value = WEEK
}
interval >= DAY -> {
checkIntervalValue.value =
- getIntervalFromUnit(interval, DAY)
+ getIntervalFromUnit(interval, DAY)
checkIntervalUnit.value = DAY
}
interval >= HOUR -> {
checkIntervalValue.value =
- getIntervalFromUnit(interval, HOUR)
+ getIntervalFromUnit(interval, HOUR)
checkIntervalUnit.value = HOUR
}
interval >= MINUTE -> {
checkIntervalValue.value =
- getIntervalFromUnit(interval, MINUTE)
+ getIntervalFromUnit(interval, MINUTE)
checkIntervalUnit.value = MINUTE
}
else -> {
diff --git a/app/src/main/res/color/unchecked_chip_text.xml b/app/src/main/res/color/unchecked_chip_text.xml
deleted file mode 100644
index 8e7f4df..0000000
--- a/app/src/main/res/color/unchecked_chip_text.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/checked_chip.xml b/app/src/main/res/drawable/checked_chip.xml
deleted file mode 100644
index 85010f5..0000000
--- a/app/src/main/res/drawable/checked_chip.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/checked_chip_pressed.xml b/app/src/main/res/drawable/checked_chip_pressed.xml
deleted file mode 100644
index 0d7c176..0000000
--- a/app/src/main/res/drawable/checked_chip_pressed.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/checked_chip_selector.xml b/app/src/main/res/drawable/checked_chip_selector.xml
deleted file mode 100644
index fa9df00..0000000
--- a/app/src/main/res/drawable/checked_chip_selector.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml
deleted file mode 100644
index 00fc15d..0000000
--- a/app/src/main/res/drawable/ic_check.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/unchecked_chip.xml b/app/src/main/res/drawable/unchecked_chip.xml
deleted file mode 100644
index 1864bc5..0000000
--- a/app/src/main/res/drawable/unchecked_chip.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/unchecked_chip_pressed.xml b/app/src/main/res/drawable/unchecked_chip_pressed.xml
deleted file mode 100644
index c387d70..0000000
--- a/app/src/main/res/drawable/unchecked_chip_pressed.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/unchecked_chip_selector.xml b/app/src/main/res/drawable/unchecked_chip_selector.xml
deleted file mode 100644
index ba01f74..0000000
--- a/app/src/main/res/drawable/unchecked_chip_selector.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/layout/activity_addsite.xml b/app/src/main/res/layout/activity_addsite.xml
index 80f83da..77765d1 100644
--- a/app/src/main/res/layout/activity_addsite.xml
+++ b/app/src/main/res/layout/activity_addsite.xml
@@ -16,7 +16,6 @@
@@ -25,61 +24,59 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:paddingBottom="@dimen/content_inset_double"
+ android:paddingBottom="@dimen/content_inset"
android:paddingLeft="@dimen/content_inset"
android:paddingRight="@dimen/content_inset"
- android:paddingTop="@dimen/content_inset_half"
>
-
+
-
+
-
+
-
+
+
+
+
+
-
-
-
-
@@ -91,10 +88,35 @@
android:layout_marginTop="@dimen/content_inset"
/>
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 1e95b7f..fb13d54 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -17,19 +17,6 @@
-
-
-
-
@@ -70,13 +55,24 @@
android:orientation="vertical"
>
+
+
-
-
@@ -126,6 +109,35 @@
android:layout_marginTop="@dimen/content_inset"
/>
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/app/src/main/res/layout/list_item_tag.xml b/app/src/main/res/layout/list_item_tag.xml
deleted file mode 100644
index 0ae5932..0000000
--- a/app/src/main/res/layout/list_item_tag.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
diff --git a/app/src/main/res/menu/menu_addsite.xml b/app/src/main/res/menu/menu_addsite.xml
deleted file mode 100644
index e346eb9..0000000
--- a/app/src/main/res/menu/menu_addsite.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
index 5f2f23f..b3b1727 100644
--- a/app/src/main/res/menu/menu_main.xml
+++ b/app/src/main/res/menu/menu_main.xml
@@ -7,4 +7,7 @@
android:id="@+id/dark_mode"
android:checkable="true"
android:title="@string/dark_mode"/>
+
diff --git a/app/src/main/res/menu/menu_viewsite.xml b/app/src/main/res/menu/menu_viewsite.xml
index fee6f5a..f46e13d 100644
--- a/app/src/main/res/menu/menu_viewsite.xml
+++ b/app/src/main/res/menu/menu_viewsite.xml
@@ -1,23 +1,17 @@
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
deleted file mode 100644
index 80b730f..0000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index 80b730f..0000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
index a86dbab..d1698c3 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
deleted file mode 100644
index eb43a7b..0000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 4567198..0000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 60056be..cccfa5b 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
deleted file mode 100644
index 666c904..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index 3baff41..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 27f30d2..a4f8356 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
deleted file mode 100644
index 4224797..0000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index 1d29a54..0000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index 60a8d1a..49dd226 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
deleted file mode 100644
index 7cf19eb..0000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index 1548eb9..0000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index ac61bd3..bd57c86 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
deleted file mode 100644
index 9c43fc9..0000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index 04806fd..0000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 84ad226..d4e6453 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -3,7 +3,6 @@
- @string/refresh_status
- - @string/duplicate_and_modify
- @string/remove_site
@@ -13,4 +12,10 @@
- JavaScript Evaluation
+
+ - via PayPal
+ - via Cash App
+ - via Venmo
+
+
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 35d3041..0df2f69 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -4,6 +4,5 @@
-
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f840cee..f6465db 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -7,11 +7,7 @@
#212121
#252525
- #303030
- #EEEEEE
-
+ #303030
#FF6E40
- #E44615
- #40FF6E40
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 510a2e9..4c19a47 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -2,6 +2,5 @@
28sp
6dp
- 4dp
diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml
deleted file mode 100644
index 70daa76..0000000
--- a/app/src/main/res/values/ic_launcher_background.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- #758F9A
-
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3ac40a0..e7506bf 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,42 +1,35 @@
Nock Nock
- Nock Nock %1$s
No sites added!
About
Aidan Follestad.
+ Nock Nock, a simple app designed by Aidan Follestad.
Website
Twitter
GitHub
LinkedIn
Nock Nock is open source! Check out the GitHub page!
Icon by Kevin Aguilar of 221 Pixels.
-
View the Privacy Policy.
]]>
Dark Mode
Dismiss
Add Site
Site Name
- Site display name
Site URL
- https://yoursite.com
- Site Tags
- e.g. One,Two,Three
- Tags (e.g. One,Two,Three)
Please enter a name!
Please enter a URL.
Please enter a valid URL.
+ Please input a validation interval.
Please input a search term.
+ Please input a validation script.
Please enter a network timeout greater than 0.
- Certificate should be a valid file or content URI.
Options
Remove Site
- Duplicate and Modify
%1$s from your sites?]]>
Remove
Save Changes
@@ -50,8 +43,8 @@
Disable Automatic Validation
%1$s? The site will not be checked in the background
- until you re-enable validation by tapping the checkmark (Save) icon. You can still manually
- perform validation by tapping the Refresh icon at the top of this page.
+ until you re-enable validation for it. You can still manually perform validation by tapping the
+ Refresh icon at the top of this page.
]]>
Disable
Enable Auto Validation & Save Changes
@@ -59,10 +52,6 @@
Network Response Timeout (ms)
10000
- SSL Certificate
- (Automatic)
- Browse
-
Refresh Status
@@ -85,6 +74,14 @@
exception to pass custom error messages to Nock Nock.
+ Donate
+ Nock Nock was created and is maintained by one person. Donations are much
+ appreciated and encourage continued support.
+ ]]>
+ Thank you very much!
+ Next
+
Please install a web browser app, such as Google Chrome.
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index e4b435a..22a2da4 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -4,9 +4,15 @@
+
+
diff --git a/app/src/main/res/values/styles_parents.xml b/app/src/main/res/values/styles_parents.xml
index dc5e3d6..decbdb8 100644
--- a/app/src/main/res/values/styles_parents.xml
+++ b/app/src/main/res/values/styles_parents.xml
@@ -9,7 +9,6 @@
- #000000
- #EEEEEE
- #000000
- - @color/lighterGray
- #212121
- #727272
@@ -34,7 +33,6 @@
- #ffffff
- #303030
- #FFFFFF
- - @color/darkerGray
- #FFFFFF
- #F0F0F0
diff --git a/app/src/main/res/values/styles_text.xml b/app/src/main/res/values/styles_text.xml
index df25036..e0fc849 100644
--- a/app/src/main/res/values/styles_text.xml
+++ b/app/src/main/res/values/styles_text.xml
@@ -6,28 +6,4 @@
- ?toolbarTitleColor
-
-
-
-
-
-
-
-
diff --git a/app/src/test/java/com/afollestad/nocknock/TestData.kt b/app/src/test/java/com/afollestad/nocknock/TestData.kt
index e5e4c29..770688b 100644
--- a/app/src/test/java/com/afollestad/nocknock/TestData.kt
+++ b/app/src/test/java/com/afollestad/nocknock/TestData.kt
@@ -19,13 +19,11 @@ import android.app.PendingIntent
import android.content.Intent
import android.content.IntentFilter
import com.afollestad.nocknock.data.AppDatabase
-import com.afollestad.nocknock.data.HeaderDao
+import com.afollestad.nocknock.data.RetryPolicy
import com.afollestad.nocknock.data.RetryPolicyDao
import com.afollestad.nocknock.data.SiteDao
import com.afollestad.nocknock.data.SiteSettingsDao
import com.afollestad.nocknock.data.ValidationResultsDao
-import com.afollestad.nocknock.data.model.Header
-import com.afollestad.nocknock.data.model.RetryPolicy
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.SiteSettings
import com.afollestad.nocknock.data.model.Status
@@ -57,8 +55,7 @@ fun fakeSettingsModel(
validationMode = validationMode,
validationArgs = null,
disabled = false,
- networkTimeout = 10000,
- certificate = null
+ networkTimeout = 10000
)
fun fakeResultModel(
@@ -82,31 +79,18 @@ fun fakeRetryPolicy(
minutes = minutes
)
-fun fakeHeaders(siteId: Long): List {
- return listOf(
- Header(id = siteId + 1, siteId = siteId, key = "Content-Type", value = "text/html"),
- Header(id = siteId + 2, siteId = siteId, key = "User-Agent", value = "NockNock")
- )
-}
-
-fun fakeModel(
- id: Long,
- tags: String = ""
-) = Site(
+fun fakeModel(id: Long) = Site(
id = id,
name = "Test",
url = "https://test.com",
- tags = tags,
settings = fakeSettingsModel(id),
lastResult = fakeResultModel(id),
- retryPolicy = fakeRetryPolicy(id),
- headers = fakeHeaders(id)
+ retryPolicy = fakeRetryPolicy(id)
)
-val MOCK_MODEL_1 = fakeModel(1, tags = "one,two")
-val MOCK_MODEL_2 = fakeModel(2, tags = "three,four")
-val MOCK_MODEL_3 = fakeModel(3, tags = "five,six")
-
+val MOCK_MODEL_1 = fakeModel(1)
+val MOCK_MODEL_2 = fakeModel(2)
+val MOCK_MODEL_3 = fakeModel(3)
val ALL_MOCK_MODELS = listOf(MOCK_MODEL_1, MOCK_MODEL_2, MOCK_MODEL_3)
fun mockDatabase(): AppDatabase {
@@ -171,29 +155,12 @@ fun mockDatabase(): AppDatabase {
on { update(isA()) } doReturn 1
on { delete(isA()) } doReturn 1
}
- val headerDao = mock {
- on { all() } doReturn MOCK_MODEL_1.headers + MOCK_MODEL_2.headers + MOCK_MODEL_3.headers
- on { forSite(isA()) } doAnswer { inv ->
- val id = inv.getArgument(0)
- return@doAnswer when (id) {
- 1L -> MOCK_MODEL_1.headers
- 2L -> MOCK_MODEL_2.headers
- 3L -> MOCK_MODEL_3.headers
- else -> listOf()
- }
- }
- on { insert(isA()) } doReturn 1L
- on { insert(isA>()) } doReturn listOf(1L, 2L)
- on { update(isA()) } doReturn 1
- on { delete(isA()) } doReturn 1
- }
return mock {
on { siteDao() } doReturn siteDao
on { siteSettingsDao() } doReturn settingsDao
on { validationResultsDao() } doReturn resultsDao
on { retryPolicyDao() } doReturn retryDao
- on { headerDao() } doReturn headerDao
}
}
diff --git a/app/src/test/java/com/afollestad/nocknock/ui/addsite/AddSiteViewModelTest.kt b/app/src/test/java/com/afollestad/nocknock/ui/addsite/AddSiteViewModelTest.kt
index 1319310..9acd165 100644
--- a/app/src/test/java/com/afollestad/nocknock/ui/addsite/AddSiteViewModelTest.kt
+++ b/app/src/test/java/com/afollestad/nocknock/ui/addsite/AddSiteViewModelTest.kt
@@ -17,21 +17,20 @@ package com.afollestad.nocknock.ui.addsite
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.afollestad.nocknock.R
-import com.afollestad.nocknock.data.model.Header
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.SiteSettings
-import com.afollestad.nocknock.data.model.Status.WAITING
import com.afollestad.nocknock.data.model.ValidationMode.JAVASCRIPT
import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE
import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH
-import com.afollestad.nocknock.data.model.ValidationResult
-import com.afollestad.nocknock.engine.validation.ValidationExecutor
+import com.afollestad.nocknock.engine.validation.ValidationManager
import com.afollestad.nocknock.mockDatabase
import com.afollestad.nocknock.utilities.ext.MINUTE
import com.afollestad.nocknock.utilities.livedata.test
import com.google.common.truth.Truth.assertThat
+import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,7 +44,7 @@ import org.junit.Test
class AddSiteViewModelTest {
private val database = mockDatabase()
- private val validationManager = mock()
+ private val validationManager = mock()
@Rule @JvmField val rule = InstantTaskExecutorRule()
@@ -150,9 +149,247 @@ class AddSiteViewModelTest {
assertThat(viewModel.getValidationArgs()).isEqualTo("Two")
}
+ @Test fun commit_nameError() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+
+ fillInModel().apply {
+ name.value = ""
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertValues(R.string.please_enter_name)
+ onUrlError.assertNoValues()
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertNoValues()
+ onScriptError.assertNoValues()
+
+ verify(onDone, never()).invoke()
+ }
+
+ @Test fun commit_urlEmptyError() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+
+ fillInModel().apply {
+ url.value = ""
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertNoValues()
+ onUrlError.assertValues(R.string.please_enter_url)
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertNoValues()
+ onScriptError.assertNoValues()
+
+ verify(onDone, never()).invoke()
+ }
+
+ @Test fun commit_urlFormatError() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+
+ fillInModel().apply {
+ url.value = "ftp://www.idk.com"
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertNoValues()
+ onUrlError.assertValues(R.string.please_enter_valid_url)
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertNoValues()
+ onScriptError.assertNoValues()
+
+ verify(onDone, never()).invoke()
+ }
+
+ @Test fun commit_networkTimeout_error() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+
+ fillInModel().apply {
+ timeout.value = 0
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertNoValues()
+ onUrlError.assertNoValues()
+ onTimeoutError.assertValues(R.string.please_enter_networkTimeout)
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertNoValues()
+ onScriptError.assertNoValues()
+
+ verify(onDone, never()).invoke()
+ }
+
+ @Test fun commit_checkIntervalError() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+
+ fillInModel().apply {
+ checkIntervalValue.value = 0
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertNoValues()
+ onUrlError.assertNoValues()
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertValues(R.string.please_enter_check_interval)
+ onSearchTermError.assertNoValues()
+ onScriptError.assertNoValues()
+
+ verify(onDone, never()).invoke()
+ }
+
+ @Test fun commit_termSearchError() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+
+ fillInModel().apply {
+ validationMode.value = TERM_SEARCH
+ validationSearchTerm.value = ""
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertNoValues()
+ onUrlError.assertNoValues()
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertValues(R.string.please_enter_search_term)
+ onScriptError.assertNoValues()
+
+ verify(onDone, never()).invoke()
+ }
+
+ @Test fun commit_javaScript_error() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+
+ fillInModel().apply {
+ validationMode.value = JAVASCRIPT
+ validationScript.value = ""
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertNoValues()
+ onUrlError.assertNoValues()
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertNoValues()
+ onScriptError.assertValues(R.string.please_enter_javaScript)
+
+ verify(onDone, never()).invoke()
+ }
+
@Test fun commit_success() = runBlocking {
val isLoading = viewModel.onIsLoading()
.test()
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
fillInModel()
val onDone = mock<() -> Unit>()
@@ -160,30 +397,31 @@ class AddSiteViewModelTest {
val siteCaptor = argumentCaptor()
val settingsCaptor = argumentCaptor()
- val validationResultCaptor = argumentCaptor()
isLoading.assertValues(true, false)
verify(database.siteDao()).insert(siteCaptor.capture())
verify(database.siteSettingsDao()).insert(settingsCaptor.capture())
- verify(database.validationResultsDao()).insert(validationResultCaptor.capture())
+ verify(database.validationResultsDao(), never()).insert(any())
val settings = settingsCaptor.firstValue
- val result = validationResultCaptor.firstValue.copy(siteId = 1)
val model = siteCaptor.firstValue.copy(
id = 1, // fill it in because our insert captor doesn't catch this
settings = settings,
- lastResult = result
+ lastResult = null
)
- assertThat(result.reason).isNull()
- assertThat(result.status).isEqualTo(WAITING)
-
- verify(validationManager).scheduleValidation(
+ verify(validationManager).scheduleCheck(
site = model,
rightNow = true,
cancelPrevious = true,
fromFinishingJob = false
)
+ onNameError.assertNoValues()
+ onUrlError.assertNoValues()
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertNoValues()
+ onScriptError.assertNoValues()
verify(onDone).invoke()
}
@@ -197,10 +435,5 @@ class AddSiteViewModelTest {
validationScript.value = null
checkIntervalValue.value = 60
checkIntervalUnit.value = 1000
- tags.value = "one,two"
- headers.value = listOf(
- Header(2L, 1L, key = "Content-Type", value = "text/html"),
- Header(3L, 1L, key = "User-Agent", value = "NockNock")
- )
}
}
diff --git a/app/src/test/java/com/afollestad/nocknock/ui/main/MainViewModelTest.kt b/app/src/test/java/com/afollestad/nocknock/ui/main/MainViewModelTest.kt
index 593996e..776c39e 100644
--- a/app/src/test/java/com/afollestad/nocknock/ui/main/MainViewModelTest.kt
+++ b/app/src/test/java/com/afollestad/nocknock/ui/main/MainViewModelTest.kt
@@ -20,7 +20,7 @@ import com.afollestad.nocknock.ALL_MOCK_MODELS
import com.afollestad.nocknock.MOCK_MODEL_1
import com.afollestad.nocknock.MOCK_MODEL_2
import com.afollestad.nocknock.MOCK_MODEL_3
-import com.afollestad.nocknock.engine.validation.ValidationExecutor
+import com.afollestad.nocknock.engine.validation.ValidationManager
import com.afollestad.nocknock.mockDatabase
import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.utilities.livedata.test
@@ -39,7 +39,7 @@ class MainViewModelTest {
private val database = mockDatabase()
private val notificationManager = mock()
- private val validationManager = mock()
+ private val validationManager = mock()
@Rule @JvmField val rule = InstantTaskExecutorRule()
@@ -60,45 +60,18 @@ class MainViewModelTest {
.test()
val sites = viewModel.onSites()
.test()
- val tags = viewModel.onTags()
- .test()
- val tagsVisibility = viewModel.onTagsListVisibility()
- .test()
viewModel.onResume()
verify(notificationManager).cancelStatusNotifications()
- verify(validationManager).ensureScheduledValidations()
+ verify(validationManager).ensureScheduledChecks()
- sites.assertValues(ALL_MOCK_MODELS)
+ sites.assertValues(
+ listOf(),
+ ALL_MOCK_MODELS
+ )
isLoading.assertValues(true, false)
emptyTextVisibility.assertValues(false, false)
- tags.assertValues(listOf("one", "two", "three", "four", "five", "six").sorted())
- tagsVisibility.assertValues(true)
- }
-
- @Test fun onTagSelection() = runBlocking {
- val isLoading = viewModel.onIsLoading()
- .test()
- val emptyTextVisibility = viewModel.onEmptyTextVisibility()
- .test()
- val sites = viewModel.onSites()
- .test()
- val tags = viewModel.onTags()
- .test()
- val tagsVisibility = viewModel.onTagsListVisibility()
- .test()
-
- viewModel.onTagSelection(listOf("four", "six"))
-
- verify(notificationManager).cancelStatusNotifications()
- verify(validationManager).ensureScheduledValidations()
-
- sites.assertValues(listOf(MOCK_MODEL_2, MOCK_MODEL_3))
- isLoading.assertValues(true, false)
- emptyTextVisibility.assertValues(false, false)
- tags.assertValues(listOf("one", "two", "three", "four", "five", "six").sorted())
- tagsVisibility.assertValues(true)
}
@Test fun postSiteUpdate_notFound() {
@@ -113,7 +86,10 @@ class MainViewModelTest {
.test()
viewModel.onResume()
- sites.assertValues(ALL_MOCK_MODELS)
+ sites.assertValues(
+ listOf(),
+ ALL_MOCK_MODELS
+ )
val updatedModel2 = MOCK_MODEL_2.copy(
name = "Wakanda Forever!!!"
@@ -130,7 +106,7 @@ class MainViewModelTest {
@Test fun refreshSite() {
viewModel.refreshSite(MOCK_MODEL_3)
- verify(validationManager).scheduleValidation(
+ verify(validationManager).scheduleCheck(
site = MOCK_MODEL_3,
rightNow = true,
cancelPrevious = true
@@ -144,7 +120,10 @@ class MainViewModelTest {
.test()
viewModel.onResume()
- sites.assertValues(ALL_MOCK_MODELS)
+ sites.assertValues(
+ listOf(),
+ ALL_MOCK_MODELS
+ )
isLoading.assertValues(true, false)
val modifiedModel = MOCK_MODEL_1.copy(id = 11111)
@@ -153,7 +132,7 @@ class MainViewModelTest {
sites.assertNoValues()
isLoading.assertValues(true, false)
- verify(validationManager).cancelScheduledValidation(modifiedModel)
+ verify(validationManager).cancelCheck(modifiedModel)
verify(notificationManager).cancelStatusNotification(modifiedModel)
verify(database.siteDao()).delete(modifiedModel)
verify(database.siteSettingsDao()).delete(modifiedModel.settings!!)
@@ -168,7 +147,10 @@ class MainViewModelTest {
.test()
viewModel.onResume()
- sites.assertValues(ALL_MOCK_MODELS)
+ sites.assertValues(
+ listOf(),
+ ALL_MOCK_MODELS
+ )
isLoading.assertValues(true, false)
val modelsWithout1 = ALL_MOCK_MODELS.toMutableList()
@@ -181,7 +163,7 @@ class MainViewModelTest {
isLoading.assertValues(true, false)
emptyTextVisibility.assertValues(false, false, false)
- verify(validationManager).cancelScheduledValidation(MOCK_MODEL_1)
+ verify(validationManager).cancelCheck(MOCK_MODEL_1)
verify(notificationManager).cancelStatusNotification(MOCK_MODEL_1)
verify(database.siteDao()).delete(MOCK_MODEL_1)
verify(database.siteSettingsDao()).delete(MOCK_MODEL_1.settings!!)
diff --git a/app/src/test/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModelTest.kt b/app/src/test/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModelTest.kt
index 47fe6e0..04e1c79 100644
--- a/app/src/test/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModelTest.kt
+++ b/app/src/test/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModelTest.kt
@@ -18,8 +18,6 @@ package com.afollestad.nocknock.ui.viewsite
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.afollestad.nocknock.MOCK_MODEL_1
import com.afollestad.nocknock.R
-import com.afollestad.nocknock.data.model.Header
-import com.afollestad.nocknock.data.model.RetryPolicy
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.SiteSettings
import com.afollestad.nocknock.data.model.Status.CHECKING
@@ -30,8 +28,7 @@ import com.afollestad.nocknock.data.model.ValidationMode.JAVASCRIPT
import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE
import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH
import com.afollestad.nocknock.data.model.ValidationResult
-import com.afollestad.nocknock.engine.validation.ValidationExecutor
-import com.afollestad.nocknock.fakeRetryPolicy
+import com.afollestad.nocknock.engine.validation.ValidationManager
import com.afollestad.nocknock.mockDatabase
import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.utilities.livedata.test
@@ -41,10 +38,9 @@ import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.doAnswer
-import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
-import com.nhaarman.mockitokotlin2.whenever
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
@@ -79,7 +75,7 @@ class ViewSiteViewModelTest {
}
}
private val database = mockDatabase()
- private val validationManager = mock()
+ private val validationManager = mock()
private val notificationManager = mock()
@Rule @JvmField val rule = InstantTaskExecutorRule()
@@ -259,11 +255,247 @@ class ViewSiteViewModelTest {
.isEqualTo("Two")
}
- @Test fun commit_success() = runBlocking {
- whenever(database.retryPolicyDao().forSite(any())).doReturn(listOf(fakeRetryPolicy(1)))
+ @Test fun commit_nameError() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+ fillInModel().apply {
+ name.value = ""
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertValues(R.string.please_enter_name)
+ onUrlError.assertNoValues()
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertNoValues()
+ onScriptError.assertNoValues()
+
+ verify(onDone, never()).invoke()
+ }
+
+ @Test fun commit_urlEmptyError() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+
+ fillInModel().apply {
+ url.value = ""
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertNoValues()
+ onUrlError.assertValues(R.string.please_enter_url)
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertNoValues()
+ onScriptError.assertNoValues()
+
+ verify(onDone, never()).invoke()
+ }
+
+ @Test fun commit_urlFormatError() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+
+ fillInModel().apply {
+ url.value = "ftp://www.idk.com"
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertNoValues()
+ onUrlError.assertValues(R.string.please_enter_valid_url)
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertNoValues()
+ onScriptError.assertNoValues()
+
+ verify(onDone, never()).invoke()
+ }
+
+ @Test fun commit_networkTimeout_error() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+
+ fillInModel().apply {
+ timeout.value = 0
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertNoValues()
+ onUrlError.assertNoValues()
+ onTimeoutError.assertValues(R.string.please_enter_networkTimeout)
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertNoValues()
+ onScriptError.assertNoValues()
+
+ verify(onDone, never()).invoke()
+ }
+
+ @Test fun commit_checkIntervalError() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+
+ fillInModel().apply {
+ checkIntervalValue.value = 0
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertNoValues()
+ onUrlError.assertNoValues()
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertValues(R.string.please_enter_check_interval)
+ onSearchTermError.assertNoValues()
+ onScriptError.assertNoValues()
+
+ verify(onDone, never()).invoke()
+ }
+
+ @Test fun commit_termSearchError() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+
+ fillInModel().apply {
+ validationMode.value = TERM_SEARCH
+ validationSearchTerm.value = ""
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertNoValues()
+ onUrlError.assertNoValues()
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertValues(R.string.please_enter_search_term)
+ onScriptError.assertNoValues()
+
+ verify(onDone, never()).invoke()
+ }
+
+ @Test fun commit_javaScript_error() {
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+
+ fillInModel().apply {
+ validationMode.value = JAVASCRIPT
+ validationScript.value = ""
+ }
+ val onDone = mock<() -> Unit>()
+ viewModel.commit(onDone)
+
+ verify(validationManager, never())
+ .scheduleCheck(any(), any(), any(), any())
+ onNameError.assertNoValues()
+ onUrlError.assertNoValues()
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertNoValues()
+ onScriptError.assertValues(R.string.please_enter_javaScript)
+
+ verify(onDone, never()).invoke()
+ }
+
+ @Test fun commit_success() = runBlocking {
val isLoading = viewModel.onIsLoading()
.test()
+ val onNameError = viewModel.onNameError()
+ .test()
+ val onUrlError = viewModel.onUrlError()
+ .test()
+ val onTimeoutError = viewModel.onTimeoutError()
+ .test()
+ val onSearchTermError = viewModel.onValidationSearchTermError()
+ .test()
+ val onScriptError = viewModel.onValidationScriptError()
+ .test()
+ val onCheckIntervalError = viewModel.onCheckIntervalError()
+ .test()
fillInModel()
val onDone = mock<() -> Unit>()
@@ -274,13 +506,11 @@ class ViewSiteViewModelTest {
val siteCaptor = argumentCaptor()
val settingsCaptor = argumentCaptor()
val resultCaptor = argumentCaptor()
- val retryPolicyCaptor = argumentCaptor()
isLoading.assertValues(true, false)
verify(database.siteDao()).update(siteCaptor.capture())
verify(database.siteSettingsDao()).update(settingsCaptor.capture())
verify(database.validationResultsDao()).update(resultCaptor.capture())
- verify(database.retryPolicyDao()).update(retryPolicyCaptor.capture())
// From fillInModel() below
val updatedSettings = MOCK_MODEL_1.settings!!.copy(
@@ -293,26 +523,31 @@ class ViewSiteViewModelTest {
val updatedResult = MOCK_MODEL_1.lastResult!!.copy(
status = WAITING
)
- val retryPolicy = retryPolicyCaptor.firstValue
val updatedModel = MOCK_MODEL_1.copy(
name = "Hello There",
url = "https://www.hellothere.com",
settings = updatedSettings,
- lastResult = updatedResult,
- retryPolicy = retryPolicy
+ lastResult = updatedResult
)
assertThat(siteCaptor.firstValue).isEqualTo(updatedModel)
assertThat(settingsCaptor.firstValue).isEqualTo(updatedSettings)
assertThat(resultCaptor.firstValue).isEqualTo(updatedResult)
- verify(validationManager).scheduleValidation(
+ verify(validationManager).scheduleCheck(
site = updatedModel,
rightNow = true,
cancelPrevious = true,
fromFinishingJob = false
)
+ onNameError.assertNoValues()
+ onUrlError.assertNoValues()
+ onTimeoutError.assertNoValues()
+ onCheckIntervalError.assertNoValues()
+ onSearchTermError.assertNoValues()
+ onScriptError.assertNoValues()
+
verify(onDone).invoke()
}
@@ -327,7 +562,7 @@ class ViewSiteViewModelTest {
)
viewModel.checkNow()
- verify(validationManager).scheduleValidation(
+ verify(validationManager).scheduleCheck(
site = expectedModel,
rightNow = true,
cancelPrevious = true
@@ -344,7 +579,7 @@ class ViewSiteViewModelTest {
viewModel.removeSite(onDone)
isLoading.assertValues(true, false)
- verify(validationManager).cancelScheduledValidation(MOCK_MODEL_1)
+ verify(validationManager).cancelCheck(MOCK_MODEL_1)
verify(notificationManager).cancelStatusNotification(MOCK_MODEL_1)
verify(database.siteDao()).delete(MOCK_MODEL_1)
verify(database.siteSettingsDao()).delete(MOCK_MODEL_1.settings!!)
@@ -368,7 +603,7 @@ class ViewSiteViewModelTest {
)
)
- verify(validationManager).cancelScheduledValidation(MOCK_MODEL_1)
+ verify(validationManager).cancelCheck(MOCK_MODEL_1)
verify(notificationManager).cancelStatusNotification(MOCK_MODEL_1)
verify(database.siteDao()).update(expectedSite)
verify(database.siteSettingsDao()).update(expectedSite.settings!!)
@@ -384,12 +619,5 @@ class ViewSiteViewModelTest {
validationScript.value = "throw 'Oh no!'"
checkIntervalValue.value = 24
checkIntervalUnit.value = 60000
- tags.value = "one,two"
- retryPolicyTimes.value = 5
- retryPolicyMinutes.value = 5
- headers.value = listOf(
- Header(2L, 1L, key = "Content-Type", value = "text/html"),
- Header(3L, 1L, key = "User-Agent", value = "NockNock")
- )
}
}
diff --git a/art/showcase5.png b/art/showcase5.png
deleted file mode 100644
index 6198f1f..0000000
Binary files a/art/showcase5.png and /dev/null differ
diff --git a/art/showcasemain3.png b/art/showcasemain3.png
new file mode 100644
index 0000000..93957b9
Binary files /dev/null and b/art/showcasemain3.png differ
diff --git a/build.gradle b/build.gradle
index 2d8e462..d99c0ee 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,7 +15,6 @@ buildscript {
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:' + versions.kotlin
classpath 'com.github.ben-manes:gradle-versions-plugin:' + versions.versionPlugin
classpath 'io.fabric.tools:gradle:' + versions.fabricPlugin
- classpath 'com.google.gms:google-services:' + versions.googleServices
}
}
@@ -23,6 +22,7 @@ allprojects {
repositories {
google()
jcenter()
+ maven { url "https://dl.bintray.com/drummer-aidan/maven" }
maven { url "https://jitpack.io" }
}
diff --git a/common/build.gradle b/common/build.gradle
index 5004349..8472928 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -12,10 +12,6 @@ android {
versionName versions.publishVersion
}
- packagingOptions {
- exclude 'META-INF/atomicfu.kotlin_module'
- }
-
// For Mozilla Rhino
lintOptions {
abortOnError false
@@ -34,7 +30,6 @@ dependencies {
implementation 'org.mozilla:rhino:' + versions.rhino
api 'com.afollestad:rxkprefs:' + versions.rxkPrefs
- api "io.reactivex.rxjava2:rxjava:" + versions.rxJava
testImplementation 'junit:junit:' + versions.junit
testImplementation 'com.google.truth:truth:' + versions.truth
diff --git a/common/src/main/java/com/afollestad/nocknock/utilities/ext/StringExt.kt b/common/src/main/java/com/afollestad/nocknock/utilities/ext/StringExt.kt
deleted file mode 100644
index 1256a27..0000000
--- a/common/src/main/java/com/afollestad/nocknock/utilities/ext/StringExt.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Designed and developed by Aidan Follestad (@afollestad)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.afollestad.nocknock.utilities.ext
-
-import android.net.Uri
-
-fun String.toUri() = Uri.parse(this)!!
-
-fun String?.isNotNullOrEmpty(): Boolean {
- if (this == null || this == "null") {
- return false
- }
- return !isNullOrEmpty()
-}
diff --git a/common/src/main/java/com/afollestad/nocknock/utilities/ext/ViewExt.kt b/common/src/main/java/com/afollestad/nocknock/utilities/ext/ViewExt.kt
index 389de77..86482e3 100644
--- a/common/src/main/java/com/afollestad/nocknock/utilities/ext/ViewExt.kt
+++ b/common/src/main/java/com/afollestad/nocknock/utilities/ext/ViewExt.kt
@@ -21,12 +21,7 @@ import android.widget.EditText
import androidx.annotation.IntRange
import kotlin.math.min
-fun EditText.setTextAndMaintainSelection(text: CharSequence?) {
- if (text == null) {
- setText("")
- return
- }
-
+fun EditText.setTextAndMaintainSelection(text: CharSequence) {
val formerStart = min(selectionStart, text.length)
val formerEnd = min(selectionEnd, text.length)
setText(text)
diff --git a/common/src/main/java/com/afollestad/nocknock/utilities/providers/IntentProvider.kt b/common/src/main/java/com/afollestad/nocknock/utilities/providers/IntentProvider.kt
index 773243f..a177c49 100644
--- a/common/src/main/java/com/afollestad/nocknock/utilities/providers/IntentProvider.kt
+++ b/common/src/main/java/com/afollestad/nocknock/utilities/providers/IntentProvider.kt
@@ -30,8 +30,6 @@ interface CanNotifyModel : Serializable {
fun notifyName(): String
fun notifyTag(): String
-
- fun notifyDescription(): String?
}
/** @author Aidan Follestad (@afollestad) */
diff --git a/common/src/main/java/com/afollestad/nocknock/utilities/providers/NotificationProvider.kt b/common/src/main/java/com/afollestad/nocknock/utilities/providers/NotificationProvider.kt
index 391fcb4..6a85298 100644
--- a/common/src/main/java/com/afollestad/nocknock/utilities/providers/NotificationProvider.kt
+++ b/common/src/main/java/com/afollestad/nocknock/utilities/providers/NotificationProvider.kt
@@ -20,7 +20,6 @@ import android.app.PendingIntent
import android.content.Context
import android.graphics.Bitmap
import androidx.core.app.NotificationCompat
-import androidx.core.app.NotificationCompat.BigTextStyle
import androidx.core.app.NotificationCompat.DEFAULT_VIBRATE
/** @author Aidan Follestad (@afollestad) */
@@ -57,10 +56,6 @@ class RealNotificationProvider(
.setLargeIcon(largeIcon)
.setAutoCancel(true)
.setDefaults(DEFAULT_VIBRATE)
- .setStyle(
- BigTextStyle()
- .bigText(content)
- )
.build()
}
}
diff --git a/common/src/test/java/com/afollestad/nocknock/utilities/livedata/DistinctTest.kt b/common/src/test/kotlin/com/afollestad/nocknock/utilities/livedata/DistinctTest.kt
similarity index 100%
rename from common/src/test/java/com/afollestad/nocknock/utilities/livedata/DistinctTest.kt
rename to common/src/test/kotlin/com/afollestad/nocknock/utilities/livedata/DistinctTest.kt
diff --git a/common/src/test/java/com/afollestad/nocknock/utilities/livedata/ZipTest.kt b/common/src/test/kotlin/com/afollestad/nocknock/utilities/livedata/ZipTest.kt
similarity index 100%
rename from common/src/test/java/com/afollestad/nocknock/utilities/livedata/ZipTest.kt
rename to common/src/test/kotlin/com/afollestad/nocknock/utilities/livedata/ZipTest.kt
diff --git a/data/build.gradle b/data/build.gradle
index e413d4c..5512756 100644
--- a/data/build.gradle
+++ b/data/build.gradle
@@ -14,10 +14,6 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
-
- packagingOptions {
- exclude 'META-INF/atomicfu.kotlin_module'
- }
}
dependencies {
diff --git a/data/src/androidTest/AndroidManifest.xml b/data/src/androidTest/AndroidManifest.xml
index c83c51d..17a2157 100644
--- a/data/src/androidTest/AndroidManifest.xml
+++ b/data/src/androidTest/AndroidManifest.xml
@@ -4,7 +4,7 @@
diff --git a/data/src/androidTest/java/com/afollestad/nocknock/data/AppDatabaseTest.kt b/data/src/androidTest/java/com/afollestad/nocknock/data/AppDatabaseTest.kt
index e7d4400..140d9c6 100644
--- a/data/src/androidTest/java/com/afollestad/nocknock/data/AppDatabaseTest.kt
+++ b/data/src/androidTest/java/com/afollestad/nocknock/data/AppDatabaseTest.kt
@@ -21,8 +21,6 @@ import android.content.Context
import androidx.room.Room.inMemoryDatabaseBuilder
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.runner.AndroidJUnit4
-import com.afollestad.nocknock.data.model.Header
-import com.afollestad.nocknock.data.model.RetryPolicy
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.SiteSettings
import com.afollestad.nocknock.data.model.Status.ERROR
@@ -48,7 +46,6 @@ class AppDatabaseTest() {
private lateinit var settingsDao: SiteSettingsDao
private lateinit var resultsDao: ValidationResultsDao
private lateinit var retryDao: RetryPolicyDao
- private lateinit var headerDao: HeaderDao
@Before fun setup() {
val context = getApplicationContext()
@@ -57,12 +54,13 @@ class AppDatabaseTest() {
settingsDao = db.siteSettingsDao()
resultsDao = db.validationResultsDao()
retryDao = db.retryPolicyDao()
- headerDao = db.headerDao()
}
@After
@Throws(IOException::class)
- fun destroy() = db.close()
+ fun destroy() {
+ db.close()
+ }
// SiteDao
@@ -70,11 +68,9 @@ class AppDatabaseTest() {
val model1 = Site(
name = "Test 1",
url = "https://test1.com",
- tags = "",
settings = null,
lastResult = null,
- retryPolicy = null,
- headers = emptyList()
+ retryPolicy = null
)
val newId1 = sitesDao.insert(model1)
assertThat(newId1).isGreaterThan(0)
@@ -82,11 +78,9 @@ class AppDatabaseTest() {
val model2 = Site(
name = "Test 2",
url = "https://test2.com",
- tags = "",
settings = null,
lastResult = null,
- retryPolicy = null,
- headers = emptyList()
+ retryPolicy = null
)
val newId2 = sitesDao.insert(model2)
assertThat(newId2).isGreaterThan(newId1)
@@ -101,11 +95,9 @@ class AppDatabaseTest() {
val model = Site(
name = "Test",
url = "https://test.com",
- tags = "",
settings = null,
lastResult = null,
- retryPolicy = null,
- headers = emptyList()
+ retryPolicy = null
)
val newId = sitesDao.insert(model)
assertThat(newId).isGreaterThan(0)
@@ -118,11 +110,9 @@ class AppDatabaseTest() {
val initialModel = Site(
name = "Test 1",
url = "https://test1.com",
- tags = "",
settings = null,
lastResult = null,
- retryPolicy = null,
- headers = emptyList()
+ retryPolicy = null
)
val newId = sitesDao.insert(initialModel)
assertThat(newId).isGreaterThan(0)
@@ -144,11 +134,9 @@ class AppDatabaseTest() {
val model1 = Site(
name = "Test 1",
url = "https://test1.com",
- tags = "",
settings = null,
lastResult = null,
- retryPolicy = null,
- headers = emptyList()
+ retryPolicy = null
)
val newId1 = sitesDao.insert(model1)
assertThat(newId1).isGreaterThan(0)
@@ -156,11 +144,9 @@ class AppDatabaseTest() {
val model2 = Site(
name = "Test 2",
url = "https://test2.com",
- tags = "",
settings = null,
lastResult = null,
- retryPolicy = null,
- headers = emptyList()
+ retryPolicy = null
)
val newId2 = sitesDao.insert(model2)
assertThat(newId2).isGreaterThan(newId1)
@@ -181,8 +167,7 @@ class AppDatabaseTest() {
validationMode = STATUS_CODE,
validationArgs = null,
disabled = false,
- networkTimeout = 10000,
- certificate = null
+ networkTimeout = 10000
)
val newId = settingsDao.insert(model)
assertThat(newId).isEqualTo(1)
@@ -200,8 +185,7 @@ class AppDatabaseTest() {
validationMode = STATUS_CODE,
validationArgs = null,
disabled = false,
- networkTimeout = 10000,
- certificate = null
+ networkTimeout = 10000
)
)
@@ -229,8 +213,7 @@ class AppDatabaseTest() {
validationMode = STATUS_CODE,
validationArgs = null,
disabled = false,
- networkTimeout = 10000,
- certificate = null
+ networkTimeout = 10000
)
)
@@ -308,7 +291,7 @@ class AppDatabaseTest() {
val newId = retryDao.insert(model)
assertThat(newId).isEqualTo(1)
- val finalModel = retryDao.forSite(newId)
+ val finalModel = resultsDao.forSite(newId)
.single()
assertThat(finalModel).isEqualTo(model.copy(siteId = newId))
}
@@ -350,78 +333,6 @@ class AppDatabaseTest() {
assertThat(retryDao.forSite(1)).isEmpty()
}
- // HeaderDao
-
- @Test fun headers_insert_and_forSite() {
- val models = listOf(
- Header(
- siteId = 1,
- key = "Name",
- value = "Aidan"
- ),
- Header(
- siteId = 1,
- key = "Born",
- value = "1995"
- )
- )
- val newIds = headerDao.insert(models)
- assertThat(newIds.first()).isEqualTo(1)
- assertThat(newIds.last()).isEqualTo(2)
-
- val finalModels = headerDao.forSite(1)
- assertThat(finalModels.first()).isEqualTo(models.first().copy(id = 1))
- assertThat(finalModels.last()).isEqualTo(models.last().copy(id = 2))
- }
-
- @Test fun headers_update() {
- val models = listOf(
- Header(
- siteId = 1,
- key = "Name",
- value = "Aidan"
- ),
- Header(
- siteId = 1,
- key = "Born",
- value = "1995"
- )
- )
- headerDao.insert(models)
-
- val insertedModel = headerDao.forSite(1)
- .last()
- val updatedModel = insertedModel.copy(
- key = "Test",
- value = "Hello"
- )
- assertThat(headerDao.update(updatedModel)).isEqualTo(1)
-
- val finalModels = headerDao.forSite(1)
- assertThat(finalModels.first()).isEqualTo(models.first().copy(id = 1))
- assertThat(finalModels.last()).isEqualTo(updatedModel)
- }
-
- @Test fun headers_delete() {
- val models = listOf(
- Header(
- siteId = 1,
- key = "Name",
- value = "Aidan"
- ),
- Header(
- siteId = 1,
- key = "Born",
- value = "1995"
- )
- )
- headerDao.insert(models)
-
- val insertedModels = headerDao.forSite(1)
- headerDao.delete(insertedModels)
- assertThat(headerDao.forSite(1)).isEmpty()
- }
-
// Extension Methods
@Test fun extension_put_and_allSites() {
@@ -431,30 +342,9 @@ class AppDatabaseTest() {
val allSites = db.allSites()
assertThat(allSites.size).isEqualTo(3)
- assertThat(allSites[0]).isEqualTo(
- MOCK_MODEL_1.copy(
- headers = listOf(
- MOCK_MODEL_1.headers.first().copy(id = 1),
- MOCK_MODEL_1.headers.last().copy(id = 2)
- )
- )
- )
- assertThat(allSites[1]).isEqualTo(
- MOCK_MODEL_2.copy(
- headers = listOf(
- MOCK_MODEL_2.headers.first().copy(id = 3),
- MOCK_MODEL_2.headers.last().copy(id = 4)
- )
- )
- )
- assertThat(allSites[2]).isEqualTo(
- MOCK_MODEL_3.copy(
- headers = listOf(
- MOCK_MODEL_3.headers.first().copy(id = 5),
- MOCK_MODEL_3.headers.last().copy(id = 6)
- )
- )
- )
+ assertThat(allSites[0]).isEqualTo(MOCK_MODEL_1)
+ assertThat(allSites[1]).isEqualTo(MOCK_MODEL_2)
+ assertThat(allSites[2]).isEqualTo(MOCK_MODEL_3)
}
@Test fun extension_put_getSite() {
@@ -489,25 +379,12 @@ class AppDatabaseTest() {
count = 4,
minutes = 8
)
- val updatedHeaders = listOf(
- modelToUpdate.headers.first().copy(
- id = 7,
- key = "One",
- value = "Hello"
- ),
- modelToUpdate.headers.last().copy(
- id = 8,
- key = "Two",
- value = "Hey"
- )
- )
val updatedModel = modelToUpdate.copy(
name = "Oijrfouhef",
url = "https://iojfdfsdk.io",
settings = updatedSettings,
lastResult = updatedValidationResult,
- retryPolicy = updatedRetryPolicy,
- headers = updatedHeaders
+ retryPolicy = updatedRetryPolicy
)
db.updateSite(updatedModel)
@@ -516,8 +393,6 @@ class AppDatabaseTest() {
assertThat(finalSite.settings).isEqualTo(updatedSettings)
assertThat(finalSite.lastResult).isEqualTo(updatedValidationResult)
assertThat(finalSite.retryPolicy).isEqualTo(updatedRetryPolicy)
- assertThat(finalSite.headers.first()).isEqualTo(updatedHeaders.first())
- assertThat(finalSite.headers.last()).isEqualTo(updatedHeaders.last())
assertThat(finalSite).isEqualTo(updatedModel)
}
@@ -527,7 +402,7 @@ class AppDatabaseTest() {
db.putSite(MOCK_MODEL_3)
val allSites = db.allSites()
- db.deleteSite(allSites[1])
+ db.deleteSite(MOCK_MODEL_2)
val remainingSettings = settingsDao.all()
assertThat(remainingSettings.size).isEqualTo(2)
@@ -543,12 +418,5 @@ class AppDatabaseTest() {
assertThat(remainingRetryPolicies.size).isEqualTo(2)
assertThat(remainingRetryPolicies[0]).isEqualTo(allSites[0].retryPolicy!!)
assertThat(remainingRetryPolicies[1]).isEqualTo(allSites[2].retryPolicy!!)
-
- val remainingHeaders = headerDao.all()
- assertThat(remainingHeaders.size).isEqualTo(4)
- assertThat(remainingHeaders[0]).isEqualTo(allSites[0].headers.first())
- assertThat(remainingHeaders[1]).isEqualTo(allSites[0].headers.last())
- assertThat(remainingHeaders[2]).isEqualTo(allSites[2].headers.first())
- assertThat(remainingHeaders[3]).isEqualTo(allSites[2].headers.last())
}
}
diff --git a/data/src/androidTest/java/com/afollestad/nocknock/data/TestUtil.kt b/data/src/androidTest/java/com/afollestad/nocknock/data/TestUtil.kt
index 2866b50..6782694 100644
--- a/data/src/androidTest/java/com/afollestad/nocknock/data/TestUtil.kt
+++ b/data/src/androidTest/java/com/afollestad/nocknock/data/TestUtil.kt
@@ -15,8 +15,6 @@
*/
package com.afollestad.nocknock.data
-import com.afollestad.nocknock.data.model.Header
-import com.afollestad.nocknock.data.model.RetryPolicy
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.SiteSettings
import com.afollestad.nocknock.data.model.Status
@@ -35,8 +33,7 @@ fun fakeSettingsModel(
validationMode = validationMode,
validationArgs = null,
disabled = false,
- networkTimeout = 10000,
- certificate = null
+ networkTimeout = 10000
)
fun fakeResultModel(
@@ -60,20 +57,13 @@ fun fakeRetryPolicy(
minutes = minutes
)
-fun fakeHeaders(siteId: Long) = listOf(
- Header(siteId = siteId, key = "Content-Type", value = "text/html"),
- Header(siteId = siteId, key = "User-Agent", value = "NockNock")
-)
-
fun fakeModel(id: Long) = Site(
id = id,
name = "Test",
url = "https://test.com",
- tags = "",
settings = fakeSettingsModel(id),
lastResult = fakeResultModel(id),
- retryPolicy = fakeRetryPolicy(id),
- headers = fakeHeaders(id)
+ retryPolicy = fakeRetryPolicy(id)
)
val MOCK_MODEL_1 = fakeModel(1)
diff --git a/data/src/main/java/com/afollestad/nocknock/data/AppDatabase.kt b/data/src/main/java/com/afollestad/nocknock/data/AppDatabase.kt
index 47687ba..c80b9f8 100644
--- a/data/src/main/java/com/afollestad/nocknock/data/AppDatabase.kt
+++ b/data/src/main/java/com/afollestad/nocknock/data/AppDatabase.kt
@@ -19,8 +19,6 @@ import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.afollestad.nocknock.data.model.Converters
-import com.afollestad.nocknock.data.model.Header
-import com.afollestad.nocknock.data.model.RetryPolicy
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.SiteSettings
import com.afollestad.nocknock.data.model.ValidationResult
@@ -28,13 +26,12 @@ import com.afollestad.nocknock.data.model.ValidationResult
/** @author Aidan Follestad (@afollestad) */
@Database(
entities = [
- Header::class,
RetryPolicy::class,
ValidationResult::class,
SiteSettings::class,
Site::class
],
- version = 5,
+ version = 2,
exportSchema = false
)
@TypeConverters(Converters::class)
@@ -47,8 +44,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun validationResultsDao(): ValidationResultsDao
abstract fun retryPolicyDao(): RetryPolicyDao
-
- abstract fun headerDao(): HeaderDao
}
/**
@@ -65,12 +60,10 @@ fun AppDatabase.allSites(): List {
.singleOrNull()
val retryPolicy = retryPolicyDao().forSite(it.id)
.singleOrNull()
- val headers = headerDao().forSite(it.id)
return@map it.copy(
settings = settings,
lastResult = lastResult,
- retryPolicy = retryPolicy,
- headers = headers
+ retryPolicy = retryPolicy
)
}
}
@@ -89,12 +82,10 @@ fun AppDatabase.getSite(id: Long): Site? {
.singleOrNull()
val retryPolicy = retryPolicyDao().forSite(id)
.singleOrNull()
- val headers = headerDao().forSite(id)
return result.copy(
settings = settings,
lastResult = lastResult,
- retryPolicy = retryPolicy,
- headers = headers
+ retryPolicy = retryPolicy
)
}
@@ -109,19 +100,14 @@ fun AppDatabase.putSite(site: Site): Site {
val settingsWithSiteId = settings.copy(siteId = newId)
val lastResultWithSiteId = site.lastResult?.copy(siteId = newId)
val retryPolicyWithSiteId = site.retryPolicy?.copy(siteId = newId)
- val headersWithSiteId = site.headers.map { it.copy(siteId = newId) }
-
siteSettingsDao().insert(settingsWithSiteId)
+
lastResultWithSiteId?.let { validationResultsDao().insert(it) }
retryPolicyWithSiteId?.let { retryPolicyDao().insert(it) }
- headerDao().insert(headersWithSiteId)
return site.copy(
id = newId,
- settings = settingsWithSiteId,
- lastResult = lastResultWithSiteId,
- retryPolicy = retryPolicyWithSiteId,
- headers = headersWithSiteId
+ settings = settingsWithSiteId
)
}
@@ -165,13 +151,6 @@ fun AppDatabase.updateSite(site: Site) {
retryPolicyDao().insert(retryPolicy)
}
}
-
- // Wipe existing headers
- headerDao().delete(headerDao().forSite(site.id))
- // Then add ones that still exist
- site.headers.forEach { header ->
- headerDao().insert(header.copy(id = 0, siteId = site.id))
- }
}
/**
@@ -183,9 +162,5 @@ fun AppDatabase.deleteSite(site: Site) {
site.settings?.let { siteSettingsDao().delete(it) }
site.lastResult?.let { validationResultsDao().delete(it) }
site.retryPolicy?.let { retryPolicyDao().delete(it) }
- if (site.headers.any { it.id == 0L }) {
- throw IllegalStateException("Cannot delete header with ID = 0.")
- }
- headerDao().delete(site.headers)
siteDao().delete(site)
}
diff --git a/data/src/main/java/com/afollestad/nocknock/data/DatabaseMigrations.kt b/data/src/main/java/com/afollestad/nocknock/data/DatabaseMigrations.kt
index 0b158b8..53a9275 100644
--- a/data/src/main/java/com/afollestad/nocknock/data/DatabaseMigrations.kt
+++ b/data/src/main/java/com/afollestad/nocknock/data/DatabaseMigrations.kt
@@ -27,45 +27,7 @@ class Database1to2Migration : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
- "CREATE TABLE IF NOT EXISTS `retry_policies` (siteId INTEGER PRIMARY KEY NOT NULL, count INTEGER NOT NULL, minutes INTEGER NOT NULL, lastTryTimestamp INTEGER NOT NULL, triesLeft INTEGER NOT NULL)"
+ "CREATE TABLE `retry_policies` (siteId INTEGER PRIMARY KEY NOT NULL, count INTEGER NOT NULL, minutes INTEGER NOT NULL)"
)
}
}
-
-/**
- * Migrates the database from version 2 to 3.
- *
- * @author Aidan Follestad (@afollestad)
- */
-class Database2to3Migration : Migration(2, 3) {
-
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE `sites` ADD COLUMN tags TEXT NOT NULL DEFAULT ''")
- }
-}
-
-/**
- * Migrates the database from version 3 to 4.
- *
- * @author Aidan Follestad (@afollestad)
- */
-class Database3to4Migration : Migration(3, 4) {
-
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL(
- "CREATE TABLE IF NOT EXISTS `headers` (id INTEGER PRIMARY KEY NOT NULL, siteId INTEGER NOT NULL, `key` TEXT NOT NULL, value TEXT NOT NULL)"
- )
- }
-}
-
-/**
- * Migrates the database from version 4 to 5.
- *
- * @author Aidan Follestad (@afollestad)
- */
-class Database4to5Migration : Migration(4, 5) {
-
- override fun migrate(database: SupportSQLiteDatabase) {
- database.execSQL("ALTER TABLE `site_settings` ADD COLUMN certificate TEXT")
- }
-}
diff --git a/data/src/main/java/com/afollestad/nocknock/data/HeaderDao.kt b/data/src/main/java/com/afollestad/nocknock/data/HeaderDao.kt
deleted file mode 100644
index c34f0cb..0000000
--- a/data/src/main/java/com/afollestad/nocknock/data/HeaderDao.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * Designed and developed by Aidan Follestad (@afollestad)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.afollestad.nocknock.data
-
-import androidx.room.Dao
-import androidx.room.Delete
-import androidx.room.Insert
-import androidx.room.OnConflictStrategy.FAIL
-import androidx.room.Query
-import androidx.room.Update
-import com.afollestad.nocknock.data.model.Header
-
-/** @author Aidan Follestad (@afollestad) */
-@Dao
-interface HeaderDao {
-
- @Query("SELECT * FROM headers ORDER BY siteId ASC")
- fun all(): List
-
- @Query("SELECT * FROM headers WHERE siteId = :siteId")
- fun forSite(siteId: Long): List
-
- @Insert(onConflict = FAIL)
- fun insert(headers: Header): Long
-
- @Insert(onConflict = FAIL)
- fun insert(headers: List): List
-
- @Update(onConflict = FAIL)
- fun update(header: Header): Int
-
- @Delete
- fun delete(headers: List): Int
-}
diff --git a/data/src/main/java/com/afollestad/nocknock/data/model/RetryPolicy.kt b/data/src/main/java/com/afollestad/nocknock/data/RetryPolicy.kt
similarity index 89%
rename from data/src/main/java/com/afollestad/nocknock/data/model/RetryPolicy.kt
rename to data/src/main/java/com/afollestad/nocknock/data/RetryPolicy.kt
index a5c7831..d3443cf 100644
--- a/data/src/main/java/com/afollestad/nocknock/data/model/RetryPolicy.kt
+++ b/data/src/main/java/com/afollestad/nocknock/data/RetryPolicy.kt
@@ -15,7 +15,7 @@
*/
@file:Suppress("unused")
-package com.afollestad.nocknock.data.model
+package com.afollestad.nocknock.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@@ -57,14 +57,6 @@ data class RetryPolicy(
return -1
}
val timesPerMinute = count.toFloat() / minutes.toFloat()
- return MINUTE / timesPerMinute.toSafeInt()
- }
-
- private fun Float.toSafeInt(): Int {
- val intValue = toInt()
- if (intValue == 0) {
- return 1
- }
- return intValue
+ return MINUTE / timesPerMinute.toInt()
}
}
diff --git a/data/src/main/java/com/afollestad/nocknock/data/RetryPolicyDao.kt b/data/src/main/java/com/afollestad/nocknock/data/RetryPolicyDao.kt
index e130fed..50f1fa2 100644
--- a/data/src/main/java/com/afollestad/nocknock/data/RetryPolicyDao.kt
+++ b/data/src/main/java/com/afollestad/nocknock/data/RetryPolicyDao.kt
@@ -21,7 +21,6 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy.FAIL
import androidx.room.Query
import androidx.room.Update
-import com.afollestad.nocknock.data.model.RetryPolicy
/** @author Aidan Follestad (@afollestad) */
@Dao
diff --git a/data/src/main/java/com/afollestad/nocknock/data/model/Header.kt b/data/src/main/java/com/afollestad/nocknock/data/model/Header.kt
deleted file mode 100644
index 9460af0..0000000
--- a/data/src/main/java/com/afollestad/nocknock/data/model/Header.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * Designed and developed by Aidan Follestad (@afollestad)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-@file:Suppress("unused")
-
-package com.afollestad.nocknock.data.model
-
-import androidx.room.Entity
-import androidx.room.PrimaryKey
-import java.io.Serializable
-
-/**
- * Represents an HTTP header that is sent with a site's validation attempts.
- *
- * @author Aidan Follestad (@afollestad)
- */
-@Entity(tableName = "headers")
-data class Header(
- /** The header's unique datrabase ID. */
- @PrimaryKey(autoGenerate = true) var id: Long = 0,
- /** The [Site] this header belong to. */
- var siteId: Long = 0,
- /** The header key/name. */
- var key: String = "",
- /** The header value. */
- var value: String = ""
-) : Serializable {
-
- constructor() : this(0, 0, "", "")
-}
diff --git a/data/src/main/java/com/afollestad/nocknock/data/model/Site.kt b/data/src/main/java/com/afollestad/nocknock/data/model/Site.kt
index 98feedc..a22314c 100644
--- a/data/src/main/java/com/afollestad/nocknock/data/model/Site.kt
+++ b/data/src/main/java/com/afollestad/nocknock/data/model/Site.kt
@@ -18,6 +18,7 @@ package com.afollestad.nocknock.data.model
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
+import com.afollestad.nocknock.data.RetryPolicy
import com.afollestad.nocknock.data.model.Status.WAITING
import com.afollestad.nocknock.utilities.ext.timeString
import com.afollestad.nocknock.utilities.providers.CanNotifyModel
@@ -31,21 +32,17 @@ data class Site(
@PrimaryKey(autoGenerate = true) var id: Long = 0,
/** The site's user-given name. */
var name: String,
- /** The URL at which validation attempts are made to. */
+ /** The URl at which validation attempts are made to. */
var url: String,
- /** Comma separated tags for this site. */
- var tags: String,
/** Settings for the site. */
@Ignore var settings: SiteSettings?,
/** The last validation attempt result for the site, if any. */
@Ignore var lastResult: ValidationResult?,
/** The site's retry policy, if any. */
- @Ignore var retryPolicy: RetryPolicy?,
- /** Request headers sent with this site's validation attempts. */
- @Ignore var headers: List
+ @Ignore var retryPolicy: RetryPolicy?
) : CanNotifyModel {
- constructor() : this(0, "", "", "", null, null, null, emptyList())
+ constructor() : this(0, "", "", null, null, null)
override fun notifyId(): Int = id.toInt()
@@ -53,8 +50,6 @@ data class Site(
override fun notifyTag(): String = url
- override fun notifyDescription() = lastResult?.reason
-
fun intervalText(): String {
requireNotNull(settings) { "Settings not queried." }
val lastCheck = lastResult?.timestampMs ?: -1
diff --git a/data/src/main/java/com/afollestad/nocknock/data/model/SiteSettings.kt b/data/src/main/java/com/afollestad/nocknock/data/model/SiteSettings.kt
index bc73589..91667a7 100644
--- a/data/src/main/java/com/afollestad/nocknock/data/model/SiteSettings.kt
+++ b/data/src/main/java/com/afollestad/nocknock/data/model/SiteSettings.kt
@@ -40,10 +40,8 @@ data class SiteSettings(
/** Whether or not the [Site] is enabled for automatic periodic checks. */
var disabled: Boolean,
/** The network response timeout for validation attempts. */
- var networkTimeout: Int,
- /** The Uri to a self signed certificate. */
- var certificate: String?
+ var networkTimeout: Int
) : Serializable {
- constructor() : this(0, 0, STATUS_CODE, null, false, 0, null)
+ constructor() : this(0, 0, STATUS_CODE, null, false, 0)
}
diff --git a/dependencies.gradle b/dependencies.gradle
index e0e2412..9432a8e 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -3,56 +3,52 @@ ext.versions = [
minSdk : 21,
compileSdk : 28,
buildTools : '28.0.3',
- publishVersion : '0.8.8',
- publishVersionCode : 46,
+ publishVersion : '0.8.2b',
+ publishVersionCode : 33,
// Plugins
- gradlePlugin : '3.4.0',
- spotlessPlugin : '3.22.0',
- versionPlugin : '0.21.0',
- googleServices : '4.2.0',
+ gradlePlugin : '3.2.1',
+ spotlessPlugin : '3.17.0',
+ versionPlugin : '0.20.0',
fabricPlugin : '1.+',
// Misc
- okHttp : '3.14.1',
+ okHttp : '3.12.1',
rhino : '1.7.10',
// Kotlin
- kotlin : '1.3.30',
- coroutines : '1.2.0',
+ kotlin : '1.3.11',
+ coroutines : '1.1.0',
koin : '1.0.2',
// Google/AndroidX
- androidxAnnotations : '1.0.2',
+ androidxAnnotations : '1.0.1',
androidxCore : '1.0.2',
androidxRecyclerView: '1.0.0',
androidxBrowser : '1.0.0',
googleMaterial : '1.0.0',
room : '2.0.0',
lifecycle : '2.0.0',
- firebaseCore : '16.0.8',
// Rx
- rxJava : '2.2.8',
rxBinding : '3.0.0-alpha1',
// afollestad
- materialDialogs : '2.8.1',
- rxkPrefs : '1.2.5',
- vvalidator : '0.4.1',
+ materialDialogs : '2.0.0-rc7',
+ rxkPrefs : '1.2.1',
// Debugging
timber : '4.7.1',
- fabric : '2.9.9@aar',
+ fabric : '2.9.8@aar',
// Unit testing
junit : '4.12',
- mockito : '2.27.0',
- mockitoKotlin : '2.1.0',
- truth : '0.44',
+ mockito : '2.23.4',
+ mockitoKotlin : '2.0.0-RC1',
+ truth : '0.42',
// UI testing
androidxTestRunner : '1.1.1',
androidxTest : '1.1.0',
- archTesting : '2.0.1'
+ archTesting : '2.0.0'
]
diff --git a/engine/src/main/java/com/afollestad/nocknock/engine/EngineModule.kt b/engine/src/main/java/com/afollestad/nocknock/engine/EngineModule.kt
index 071960c..fbd3a45 100644
--- a/engine/src/main/java/com/afollestad/nocknock/engine/EngineModule.kt
+++ b/engine/src/main/java/com/afollestad/nocknock/engine/EngineModule.kt
@@ -15,18 +15,14 @@
*/
package com.afollestad.nocknock.engine
-import com.afollestad.nocknock.engine.ssl.RealSslManager
-import com.afollestad.nocknock.engine.ssl.SslManager
-import com.afollestad.nocknock.engine.validation.RealValidationExecutor
-import com.afollestad.nocknock.engine.validation.ValidationExecutor
+import com.afollestad.nocknock.engine.validation.RealValidationManager
+import com.afollestad.nocknock.engine.validation.ValidationManager
import org.koin.dsl.module.module
/** @author Aidan Follestad (@afollestad) */
val engineModule = module {
single {
- RealValidationExecutor(get(), get(), get(), get(), get(), get(), get())
- } bind ValidationExecutor::class
-
- factory { RealSslManager(get()) } bind SslManager::class
+ RealValidationManager(get(), get(), get(), get(), get(), get())
+ } bind ValidationManager::class
}
diff --git a/engine/src/main/java/com/afollestad/nocknock/engine/ssl/SslManager.kt b/engine/src/main/java/com/afollestad/nocknock/engine/ssl/SslManager.kt
deleted file mode 100644
index aceeb22..0000000
--- a/engine/src/main/java/com/afollestad/nocknock/engine/ssl/SslManager.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/**
- * Designed and developed by Aidan Follestad (@afollestad)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.afollestad.nocknock.engine.ssl
-
-import android.app.Application
-import android.content.Context
-import android.net.Uri
-import androidx.annotation.CheckResult
-import com.afollestad.nocknock.utilities.ext.toUri
-import okhttp3.OkHttpClient
-import java.io.BufferedInputStream
-import java.io.FileInputStream
-import java.security.KeyStore
-import java.security.cert.CertificateFactory
-import javax.net.ssl.SSLContext
-import javax.net.ssl.TrustManagerFactory
-import javax.net.ssl.X509TrustManager
-import timber.log.Timber.d as log
-
-/** @author Aidan Follestad (@afollestad) */
-interface SslManager {
-
- @CheckResult fun clientForCertificate(
- certUri: String,
- siteUri: String,
- client: OkHttpClient
- ): OkHttpClient
-}
-
-/** @author Aidan Follestad (@afollestad) **/
-class RealSslManager(
- private val app: Application
-) : SslManager {
-
- override fun clientForCertificate(
- certUri: String,
- siteUri: String,
- client: OkHttpClient
- ): OkHttpClient {
- val parsedCertUri = certUri.toUri()
- val parsedSiteUri = siteUri.toUri()
- val siteHost = parsedSiteUri.host ?: ""
-
- log("Loading certificate $certUri for host $siteHost")
- val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
- keyStore.load(null, null)
-
- val certInputStream = app.openUri(parsedCertUri)
- val bis = BufferedInputStream(certInputStream)
- val certificateFactory = CertificateFactory.getInstance("X.509")
-
- while (bis.available() > 0) {
- val cert = certificateFactory.generateCertificate(bis)
- keyStore.setCertificateEntry(siteHost, cert)
- }
-
- val trustManagerFactory =
- TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
- trustManagerFactory.init(keyStore)
-
- val trustManagers = trustManagerFactory.trustManagers
- val sslContext = SSLContext.getInstance("TLS")
- sslContext.init(null, trustManagers, null)
-
- val trustManager = trustManagers.first() as X509TrustManager
- log("Loaded successfully!")
- return client.newBuilder()
- .sslSocketFactory(sslContext.socketFactory, trustManager)
- .hostnameVerifier { hostname, _ ->
- log("Verifying hostname $hostname")
- hostname == siteHost
- }
- .build()
- }
-}
-
-private fun Context.openUri(uri: Uri) = when (uri.scheme) {
- "content" -> {
- contentResolver.openInputStream(uri) ?: throw IllegalStateException(
- "Unable to open input stream to $uri"
- )
- }
- "file" -> FileInputStream(uri.path)
- else -> FileInputStream(uri.toString())
-}
diff --git a/engine/src/main/java/com/afollestad/nocknock/engine/validation/BootReceiver.kt b/engine/src/main/java/com/afollestad/nocknock/engine/validation/BootReceiver.kt
index cc80509..deab303 100644
--- a/engine/src/main/java/com/afollestad/nocknock/engine/validation/BootReceiver.kt
+++ b/engine/src/main/java/com/afollestad/nocknock/engine/validation/BootReceiver.kt
@@ -32,7 +32,7 @@ import timber.log.Timber.d as log
/** @author Aidan Follestad (@afollestad) */
class BootReceiver : BroadcastReceiver(), KoinComponent {
- private val validationManager by inject()
+ private val validationManager by inject()
private val mainDispatcher by inject(name = MAIN_DISPATCHER)
private val ioDispatcher by inject(name = IO_DISPATCHER)
@@ -48,7 +48,7 @@ class BootReceiver : BroadcastReceiver(), KoinComponent {
val pendingResult = goAsync()
GlobalScope.launch(mainDispatcher) {
- withContext(ioDispatcher) { validationManager.ensureScheduledValidations() }
+ withContext(ioDispatcher) { validationManager.ensureScheduledChecks() }
pendingResult.resultCode = 0
pendingResult.finish()
}
diff --git a/engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationJob.kt b/engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationJob.kt
index 435ff89..7e45f19 100644
--- a/engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationJob.kt
+++ b/engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationJob.kt
@@ -19,8 +19,8 @@ import android.app.job.JobParameters
import android.app.job.JobService
import android.content.Intent
import com.afollestad.nocknock.data.AppDatabase
+import com.afollestad.nocknock.data.RetryPolicy
import com.afollestad.nocknock.data.getSite
-import com.afollestad.nocknock.data.model.RetryPolicy
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.Status
import com.afollestad.nocknock.data.model.Status.CHECKING
@@ -60,7 +60,7 @@ class ValidationJob : JobService() {
}
private val database by inject()
- private val validationManager by inject()
+ private val validationManager by inject()
private val notificationManager by inject()
override fun onStartJob(params: JobParameters): Boolean {
@@ -80,14 +80,10 @@ class ValidationJob : JobService() {
sendBroadcast(Intent(ACTION_JOB_RUNNING).apply { putExtra(KEY_SITE_ID, site.id) })
log("Checking ${site.name} (${site.url})...")
- val lastResult = site.lastResult
- if (lastResult != null) {
- log("Result of previous attempt: ${lastResult.status}")
- }
val jobResult = async(IO) {
updateStatus(site, CHECKING)
- val checkResult = validationManager.performValidation(site)
+ val checkResult = validationManager.performCheck(site)
val resultModel = checkResult.model
val resultResponse = checkResult.response
val result = resultModel.lastResult!!
@@ -143,9 +139,6 @@ class ValidationJob : JobService() {
if (jobResult.lastResult!!.status == OK) {
notificationManager.cancelStatusNotification(jobResult)
- if (lastResult != null && lastResult.status == ERROR) {
- notificationManager.postValidationSuccessNotification(jobResult)
- }
} else {
val retryPolicy = site.retryPolicy
if (retryPolicy != null) {
@@ -160,7 +153,7 @@ class ValidationJob : JobService() {
updateTriesLeft(retryPolicy, retryPolicy.triesLeft)
val interval = retryPolicy.interval()
- validationManager.scheduleValidation(
+ validationManager.scheduleCheck(
site = jobResult,
fromFinishingJob = true,
overrideDelay = interval
@@ -174,10 +167,10 @@ class ValidationJob : JobService() {
}
}
- notificationManager.postValidationErrorNotification(jobResult)
+ notificationManager.postStatusNotification(jobResult)
}
- validationManager.scheduleValidation(
+ validationManager.scheduleCheck(
site = jobResult,
fromFinishingJob = true
)
@@ -232,7 +225,6 @@ class ValidationJob : JobService() {
triesLeft: Int
) {
retryPolicy.triesLeft = triesLeft
- retryPolicy.lastTryTimestamp = currentTimeMillis()
withContext(IO) {
database.retryPolicyDao()
.update(retryPolicy)
diff --git a/engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationExecutor.kt b/engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationManager.kt
similarity index 65%
rename from engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationExecutor.kt
rename to engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationManager.kt
index d47d616..87593fc 100644
--- a/engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationExecutor.kt
+++ b/engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationManager.kt
@@ -17,26 +17,21 @@ package com.afollestad.nocknock.engine.validation
import android.app.job.JobScheduler
import android.app.job.JobScheduler.RESULT_SUCCESS
-import android.net.Uri
import com.afollestad.nocknock.data.AppDatabase
import com.afollestad.nocknock.data.allSites
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.Status.ERROR
import com.afollestad.nocknock.data.model.Status.OK
import com.afollestad.nocknock.engine.R
-import com.afollestad.nocknock.engine.ssl.SslManager
import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.KEY_SITE_ID
-import com.afollestad.nocknock.utilities.ext.isNotNullOrEmpty
import com.afollestad.nocknock.utilities.providers.BundleProvider
import com.afollestad.nocknock.utilities.providers.JobInfoProvider
import com.afollestad.nocknock.utilities.providers.StringProvider
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
-import org.jetbrains.annotations.TestOnly
import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit.MILLISECONDS
-import kotlin.math.max
import timber.log.Timber.d as log
/** @author Aidan Follestad (@afollestad) */
@@ -47,14 +42,12 @@ data class CheckResult(
typealias ClientTimeoutChanger = (client: OkHttpClient, timeout: Int) -> OkHttpClient
-typealias UriConverter = (String) -> Uri
-
/** @author Aidan Follestad (@afollestad) */
-interface ValidationExecutor {
+interface ValidationManager {
- suspend fun ensureScheduledValidations()
+ suspend fun ensureScheduledChecks()
- fun scheduleValidation(
+ fun scheduleCheck(
site: Site,
rightNow: Boolean = false,
cancelPrevious: Boolean = rightNow,
@@ -62,20 +55,19 @@ interface ValidationExecutor {
overrideDelay: Long = -1
)
- fun cancelScheduledValidation(site: Site)
+ fun cancelCheck(site: Site)
- suspend fun performValidation(site: Site): CheckResult
+ suspend fun performCheck(site: Site): CheckResult
}
-class RealValidationExecutor(
+class RealValidationManager(
private val jobScheduler: JobScheduler,
private val okHttpClient: OkHttpClient,
private val stringProvider: StringProvider,
private val bundleProvider: BundleProvider,
private val jobInfoProvider: JobInfoProvider,
- private val database: AppDatabase,
- private val sslManager: SslManager
-) : ValidationExecutor {
+ private val database: AppDatabase
+) : ValidationManager {
private var clientTimeoutChanger: ClientTimeoutChanger = { client, timeout ->
client.newBuilder()
@@ -83,37 +75,37 @@ class RealValidationExecutor(
.build()
}
- override suspend fun ensureScheduledValidations() {
+ override suspend fun ensureScheduledChecks() {
val sites = database.allSites()
if (sites.isEmpty()) {
return
}
- log("Ensuring enabled sites have scheduled validations.")
+ log("Ensuring enabled sites have scheduled checks.")
sites.filter { it.settings?.disabled != true }
.forEach { site ->
val existingJob = jobForSite(site)
if (existingJob == null) {
log("Site ${site.id} does NOT have a scheduled job, running one now.")
- scheduleValidation(site = site, rightNow = true)
+ scheduleCheck(site = site, rightNow = true)
} else {
log("Site ${site.id} already has a scheduled job. Nothing to do.")
}
}
}
- override fun scheduleValidation(
+ override fun scheduleCheck(
site: Site,
rightNow: Boolean,
cancelPrevious: Boolean,
fromFinishingJob: Boolean,
overrideDelay: Long
) {
- check(site.id != 0L) { "Cannot schedule validations for jobs with no ID." }
+ check(site.id != 0L) { "Cannot schedule checks for jobs with no ID." }
val siteSettings = site.settings
requireNotNull(siteSettings) { "Site settings must be populated." }
if (cancelPrevious) {
- cancelScheduledValidation(site)
+ cancelCheck(site)
} else if (!fromFinishingJob) {
val existingJob = jobForSite(site)
check(existingJob == null) {
@@ -121,7 +113,7 @@ class RealValidationExecutor(
}
}
- log("Requesting a validation job for site to be scheduled: $site")
+ log("Requesting a check job for site to be scheduled: $site")
val extras = bundleProvider.createPersistable {
putLong(KEY_SITE_ID, site.id)
}
@@ -139,59 +131,43 @@ class RealValidationExecutor(
val dispatchResult = jobScheduler.schedule(jobInfo)
if (dispatchResult != RESULT_SUCCESS) {
- log("Failed to schedule a validation job for site: ${site.id}")
+ log("Failed to schedule a check job for site: ${site.id}")
} else {
- log("Validation job successfully scheduled for site: ${site.id}")
+ log("Check job successfully scheduled for site: ${site.id}")
}
}
- override fun cancelScheduledValidation(site: Site) {
- check(site.id != 0L) { "Cannot cancel scheduled validations for jobs with no ID." }
- log("Cancelling scheduled validations for site: ${site.id}")
+ override fun cancelCheck(site: Site) {
+ check(site.id != 0L) { "Cannot cancel scheduled checks for jobs with no ID." }
+ log("Cancelling scheduled checks for site: ${site.id}")
jobScheduler.cancel(site.id.toInt())
}
- override suspend fun performValidation(site: Site): CheckResult {
- check(site.id != 0L) { "Cannot schedule validations for jobs with no ID." }
+ override suspend fun performCheck(site: Site): CheckResult {
+ check(site.id != 0L) { "Cannot schedule checks for jobs with no ID." }
val siteSettings = site.settings
requireNotNull(siteSettings) { "Site settings must be populated." }
- log("performValidation(${site.id}) - GET ${site.url}")
+ check(siteSettings.networkTimeout > 0) { "Network timeout not set for site ${site.id}" }
+ log("performCheck(${site.id}) - GET ${site.url}")
val request = Request.Builder()
- .apply {
- url(site.url)
- get()
- site.headers
- .filter { header -> header.key.isNotNullOrEmpty() }
- .forEach { header ->
- addHeader(header.key, header.value)
- }
- }
+ .url(site.url)
+ .get()
.build()
return try {
- val timeout = max(siteSettings.networkTimeout, 1)
- val clientWithTimeout = clientTimeoutChanger(okHttpClient, timeout)
- val client = if (siteSettings.certificate.isNotNullOrEmpty()) {
- sslManager.clientForCertificate(
- certUri = siteSettings.certificate!!,
- siteUri = site.url,
- client = clientWithTimeout
- )
- } else {
- clientWithTimeout
- }
+ val client = clientTimeoutChanger(okHttpClient, siteSettings.networkTimeout)
val response = client.newCall(request)
.execute()
- if (response.isSuccessful) {
- log("performValidation(${site.id}) = Successful")
+ if (response.isSuccessful || response.code() == 401) {
+ log("performCheck(${site.id}) = Successful")
CheckResult(
model = site.withStatus(status = OK, reason = null),
response = response
)
} else {
- log("performValidation(${site.id}) = Failure, HTTP code ${response.code()}")
+ log("performCheck(${site.id}) = Failure, HTTP code ${response.code()}")
CheckResult(
model = site.withStatus(
status = ERROR,
@@ -201,7 +177,7 @@ class RealValidationExecutor(
)
}
} catch (timeoutEx: SocketTimeoutException) {
- log("performValidation(${site.id}) = Socket Timeout")
+ log("performCheck(${site.id}) = Socket Timeout")
CheckResult(
model = site.withStatus(
status = ERROR,
@@ -209,8 +185,7 @@ class RealValidationExecutor(
)
)
} catch (ex: Exception) {
- ex.printStackTrace()
- log("performValidation(${site.id}) = Error: ${ex.message}")
+ log("performCheck(${site.id}) = Error: ${ex.message}")
CheckResult(model = site.withStatus(status = ERROR, reason = ex.message))
}
}
@@ -219,7 +194,7 @@ class RealValidationExecutor(
jobScheduler.allPendingJobs
.firstOrNull { job -> job.id == site.id.toInt() }
- @TestOnly fun setClientTimeoutChanger(changer: ClientTimeoutChanger) {
- this.clientTimeoutChanger = changer
- }
+// @TestOnly fun setClientTimeoutChanger(changer: ClientTimeoutChanger) {
+// this.clientTimeoutChanger = changer
+// }
}
diff --git a/engine/src/test/java/com/afollestad/nocknock/engine/TestData.kt b/engine/src/test/java/com/afollestad/nocknock/engine/TestData.kt
deleted file mode 100644
index cfe4a56..0000000
--- a/engine/src/test/java/com/afollestad/nocknock/engine/TestData.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/**
- * Designed and developed by Aidan Follestad (@afollestad)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.afollestad.nocknock.engine
-
-import android.content.Intent
-import com.afollestad.nocknock.data.AppDatabase
-import com.afollestad.nocknock.data.HeaderDao
-import com.afollestad.nocknock.data.RetryPolicyDao
-import com.afollestad.nocknock.data.SiteDao
-import com.afollestad.nocknock.data.SiteSettingsDao
-import com.afollestad.nocknock.data.ValidationResultsDao
-import com.afollestad.nocknock.data.model.Header
-import com.afollestad.nocknock.data.model.RetryPolicy
-import com.afollestad.nocknock.data.model.Site
-import com.afollestad.nocknock.data.model.SiteSettings
-import com.afollestad.nocknock.data.model.Status
-import com.afollestad.nocknock.data.model.Status.OK
-import com.afollestad.nocknock.data.model.ValidationMode
-import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE
-import com.afollestad.nocknock.data.model.ValidationResult
-import com.nhaarman.mockitokotlin2.doAnswer
-import com.nhaarman.mockitokotlin2.doReturn
-import com.nhaarman.mockitokotlin2.isA
-import com.nhaarman.mockitokotlin2.mock
-import java.lang.System.currentTimeMillis
-
-fun fakeIntent(action: String): Intent {
- return mock {
- on { getAction() } doReturn action
- }
-}
-
-fun fakeSettingsModel(
- id: Long,
- validationMode: ValidationMode = STATUS_CODE
-) = SiteSettings(
- siteId = id,
- validationIntervalMs = 600000,
- validationMode = validationMode,
- validationArgs = null,
- disabled = false,
- networkTimeout = 10000,
- certificate = null
-)
-
-fun fakeResultModel(
- id: Long,
- status: Status = OK,
- reason: String? = null
-) = ValidationResult(
- siteId = id,
- status = status,
- reason = reason,
- timestampMs = currentTimeMillis()
-)
-
-fun fakeRetryPolicy(
- id: Long,
- count: Int = 3,
- minutes: Int = 6
-) = RetryPolicy(
- siteId = id,
- count = count,
- minutes = minutes
-)
-
-fun fakeHeaders(siteId: Long): List {
- return listOf(
- Header(id = siteId + 1, siteId = siteId, key = "Content-Type", value = "text/html"),
- Header(id = siteId + 2, siteId = siteId, key = "User-Agent", value = "NockNock")
- )
-}
-
-fun fakeModel(id: Long) = Site(
- id = id,
- name = "Test",
- url = "https://test.com",
- tags = "",
- settings = fakeSettingsModel(id),
- lastResult = fakeResultModel(id),
- retryPolicy = fakeRetryPolicy(id),
- headers = fakeHeaders(id)
-)
-
-val MOCK_MODEL_1 = fakeModel(1)
-val MOCK_MODEL_2 = fakeModel(2)
-val MOCK_MODEL_3 = fakeModel(3)
-val ALL_MOCK_MODELS = listOf(MOCK_MODEL_1, MOCK_MODEL_2, MOCK_MODEL_3)
-
-fun mockDatabase(): AppDatabase {
- val siteDao = mock {
- on { insert(isA()) } doReturn 1
- on { one(isA()) } doAnswer { inv ->
- val id = inv.getArgument(0)
- return@doAnswer when (id) {
- 1L -> listOf(MOCK_MODEL_1)
- 2L -> listOf(MOCK_MODEL_2)
- 3L -> listOf(MOCK_MODEL_3)
- else -> listOf()
- }
- }
- on { all() } doReturn ALL_MOCK_MODELS
- on { update(isA()) } doAnswer { inv ->
- return@doAnswer inv.arguments.size
- }
- on { delete(isA()) } doAnswer { inv ->
- return@doAnswer inv.arguments.size
- }
- }
- val settingsDao = mock {
- on { insert(isA()) } doReturn 1L
- on { forSite(isA()) } doAnswer { inv ->
- val id = inv.getArgument(0)
- return@doAnswer when (id) {
- 1L -> listOf(MOCK_MODEL_1.settings!!)
- 2L -> listOf(MOCK_MODEL_2.settings!!)
- 3L -> listOf(MOCK_MODEL_3.settings!!)
- else -> listOf()
- }
- }
- on { update(isA()) } doReturn 1
- on { delete(isA()) } doReturn 1
- }
- val resultsDao = mock {
- on { insert(isA()) } doReturn 1L
- on { forSite(isA()) } doAnswer { inv ->
- val id = inv.getArgument(0)
- return@doAnswer when (id) {
- 1L -> listOf(MOCK_MODEL_1.lastResult!!)
- 2L -> listOf(MOCK_MODEL_2.lastResult!!)
- 3L -> listOf(MOCK_MODEL_3.lastResult!!)
- else -> listOf()
- }
- }
- on { update(isA()) } doReturn 1
- on { delete(isA()) } doReturn 1
- }
- val retryDao = mock {
- on { insert(isA()) } doReturn 1L
- on { forSite(isA()) } doAnswer { inv ->
- val id = inv.getArgument(0)
- return@doAnswer when (id) {
- 1L -> listOf(MOCK_MODEL_1.retryPolicy!!)
- 2L -> listOf(MOCK_MODEL_2.retryPolicy!!)
- 3L -> listOf(MOCK_MODEL_3.retryPolicy!!)
- else -> listOf()
- }
- }
- on { update(isA()) } doReturn 1
- on { delete(isA()) } doReturn 1
- }
- val headerDao = mock {
- on { all() } doReturn MOCK_MODEL_1.headers + MOCK_MODEL_2.headers + MOCK_MODEL_3.headers
- on { forSite(isA()) } doAnswer { inv ->
- val id = inv.getArgument(0)
- return@doAnswer when (id) {
- 1L -> MOCK_MODEL_1.headers
- 2L -> MOCK_MODEL_2.headers
- 3L -> MOCK_MODEL_3.headers
- else -> listOf()
- }
- }
- on { insert(isA()) } doReturn 1L
- on { insert(isA>()) } doReturn listOf(1L, 2L)
- on { update(isA()) } doReturn 1
- on { delete(isA()) } doReturn 1
- }
-
- return mock {
- on { siteDao() } doReturn siteDao
- on { siteSettingsDao() } doReturn settingsDao
- on { validationResultsDao() } doReturn resultsDao
- on { retryPolicyDao() } doReturn retryDao
- on { headerDao() } doReturn headerDao
- }
-}
diff --git a/engine/src/test/java/com/afollestad/nocknock/engine/ValidationExecutorTest.kt b/engine/src/test/kotlin/com/afollestad/nocknock/engine/CheckStatusManagerTest.kt
similarity index 55%
rename from engine/src/test/java/com/afollestad/nocknock/engine/ValidationExecutorTest.kt
rename to engine/src/test/kotlin/com/afollestad/nocknock/engine/CheckStatusManagerTest.kt
index dff3217..db859f4 100644
--- a/engine/src/test/java/com/afollestad/nocknock/engine/ValidationExecutorTest.kt
+++ b/engine/src/test/kotlin/com/afollestad/nocknock/engine/CheckStatusManagerTest.kt
@@ -17,12 +17,13 @@ package com.afollestad.nocknock.engine
import android.app.job.JobInfo
import android.app.job.JobScheduler
-import com.afollestad.nocknock.data.model.Header
+import com.afollestad.nocknock.data.legacy.ServerModel
import com.afollestad.nocknock.data.model.Status.ERROR
import com.afollestad.nocknock.data.model.Status.OK
-import com.afollestad.nocknock.engine.ssl.SslManager
-import com.afollestad.nocknock.engine.validation.RealValidationExecutor
+import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE
+import com.afollestad.nocknock.data.legacy.ServerModelStore
import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.KEY_SITE_ID
+import com.afollestad.nocknock.engine.validation.RealValidationManager
import com.afollestad.nocknock.utilities.providers.StringProvider
import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.any
@@ -44,7 +45,7 @@ import okhttp3.ResponseBody
import org.junit.Test
import java.net.SocketTimeoutException
-class ValidationExecutorTest {
+class CheckStatusManagerTest {
private val timeoutError = "Oh no, a timeout"
@@ -55,21 +56,15 @@ class ValidationExecutorTest {
}
private val bundleProvider = testBundleProvider()
private val jobInfoProvider = testJobInfoProvider()
- private val database = mockDatabase()
- private val sslManager = mock {
- on { clientForCertificate(any(), any(), any()) } doAnswer { inv ->
- inv.getArgument(2)
- }
- }
+ private val store = mock()
- private val manager = RealValidationExecutor(
+ private val manager = RealValidationManager(
jobScheduler,
okHttpClient,
stringProvider,
bundleProvider,
jobInfoProvider,
- database,
- sslManager
+ store
).apply {
setClientTimeoutChanger { _, timeout ->
whenever(okHttpClient.callTimeoutMillis()).doReturn(timeout)
@@ -77,241 +72,202 @@ class ValidationExecutorTest {
}
}
- @Test fun ensureScheduledValidations_noEnabledSites() = runBlocking {
- val model1 = fakeModel(id = 1)
- model1.settings = model1.settings!!.copy(disabled = true)
- database.setAllSites(model1)
+ @Test fun ensureScheduledChecks_noEnabledSites() = runBlocking {
+ val model1 = fakeModel().copy(disabled = true)
+ whenever(store.get()).doReturn(listOf(model1))
- manager.ensureScheduledValidations()
+ manager.ensureScheduledChecks()
verifyNoMoreInteractions(jobScheduler)
}
- @Test fun ensureScheduledValidations_sitesAlreadyHaveJobs() = runBlocking {
- val model1 = fakeModel(id = 1)
- val job1 = fakeJob(1)
- database.setAllSites(model1)
+ @Test fun ensureScheduledChecks_sitesAlreadyHaveJobs() = runBlocking {
+ val model1 = fakeModel()
+ val job1 = fakeJob(model1.id)
+ whenever(store.get()).doReturn(listOf(model1))
whenever(jobScheduler.allPendingJobs).doReturn(listOf(job1))
- manager.ensureScheduledValidations()
+ manager.ensureScheduledChecks()
verify(jobScheduler, never()).schedule(any())
}
- @Test fun ensureScheduledValidations() = runBlocking {
- val model1 = fakeModel(id = 1)
- database.setAllSites(model1)
-
+ @Test fun ensureScheduledChecks() = runBlocking {
+ val model1 = fakeModel()
+ whenever(store.get()).doReturn(listOf(model1))
whenever(jobScheduler.allPendingJobs).doReturn(listOf())
- manager.ensureScheduledValidations()
+ manager.ensureScheduledChecks()
val jobCaptor = argumentCaptor()
verify(jobScheduler).schedule(jobCaptor.capture())
val jobInfo = jobCaptor.allValues.single()
assertThat(jobInfo.id).isEqualTo(model1.id)
- assertThat(jobInfo.extras.getLong(KEY_SITE_ID)).isEqualTo(model1.id)
+ assertThat(jobInfo.extras.getInt(KEY_SITE_ID)).isEqualTo(model1.id)
}
- @Test fun scheduleValidation_rightNow() {
- val model1 = fakeModel(id = 1)
+ @Test fun scheduleCheck_rightNow() {
+ val model1 = fakeModel()
whenever(jobScheduler.allPendingJobs).doReturn(listOf())
- manager.scheduleValidation(
+ manager.scheduleCheck(
site = model1,
rightNow = true
)
val jobCaptor = argumentCaptor()
verify(jobScheduler).schedule(jobCaptor.capture())
- verify(jobScheduler).cancel(1)
+ verify(jobScheduler).cancel(model1.id)
val jobInfo = jobCaptor.allValues.single()
assertThat(jobInfo.id).isEqualTo(model1.id)
- assertThat(jobInfo.extras.getLong(KEY_SITE_ID)).isEqualTo(model1.id)
+ assertThat(jobInfo.extras.getInt(KEY_SITE_ID)).isEqualTo(model1.id)
}
@Test(expected = IllegalStateException::class)
- fun scheduleValidation_notFromFinishingJob_haveExistingJob() {
- val model1 = fakeModel(id = 1)
- val job1 = fakeJob(1)
+ fun scheduleCheck_notFromFinishingJob_haveExistingJob() {
+ val model1 = fakeModel()
+ val job1 = fakeJob(model1.id)
whenever(jobScheduler.allPendingJobs).doReturn(listOf(job1))
- manager.scheduleValidation(
+ manager.scheduleCheck(
site = model1,
fromFinishingJob = false
)
}
- @Test fun scheduleValidation_fromFinishingJob_haveExistingJob() {
- val model1 = fakeModel(id = 1)
- val job1 = fakeJob(1)
+ @Test fun scheduleCheck_fromFinishingJob_haveExistingJob() {
+ val model1 = fakeModel()
+ val job1 = fakeJob(model1.id)
whenever(jobScheduler.allPendingJobs).doReturn(listOf(job1))
- manager.scheduleValidation(
+ manager.scheduleCheck(
site = model1,
fromFinishingJob = true
)
val jobCaptor = argumentCaptor()
verify(jobScheduler).schedule(jobCaptor.capture())
- verify(jobScheduler, never()).cancel(any())
+ verify(jobScheduler, never()).cancel(model1.id)
val jobInfo = jobCaptor.allValues.single()
assertThat(jobInfo.id).isEqualTo(model1.id)
- assertThat(jobInfo.extras.getLong(KEY_SITE_ID)).isEqualTo(model1.id)
+ assertThat(jobInfo.extras.getInt(KEY_SITE_ID)).isEqualTo(model1.id)
}
- @Test fun scheduleValidation() {
- val model1 = fakeModel(id = 1)
+ @Test fun scheduleCheck() {
+ val model1 = fakeModel()
whenever(jobScheduler.allPendingJobs).doReturn(listOf())
- manager.scheduleValidation(
+ manager.scheduleCheck(
site = model1,
fromFinishingJob = true
)
val jobCaptor = argumentCaptor()
verify(jobScheduler).schedule(jobCaptor.capture())
- verify(jobScheduler, never()).cancel(any())
+ verify(jobScheduler, never()).cancel(model1.id)
val jobInfo = jobCaptor.allValues.single()
assertThat(jobInfo.id).isEqualTo(model1.id)
- assertThat(jobInfo.extras.getLong(KEY_SITE_ID)).isEqualTo(model1.id)
+ assertThat(jobInfo.extras.getInt(KEY_SITE_ID)).isEqualTo(model1.id)
}
- @Test fun cancelScheduledValidation() {
- val model1 = fakeModel(id = 1)
- manager.cancelScheduledValidation(model1)
- verify(jobScheduler).cancel(1)
+ @Test fun cancelCheck() {
+ val model1 = fakeModel()
+ manager.cancelCheck(model1)
+ verify(jobScheduler).cancel(model1.id)
}
- @Test fun performValidation_httpNotSuccess() = runBlocking {
+ @Test fun performCheck_httpNotSuccess() = runBlocking {
val response = fakeResponse(500, "Internal Server Error", "Hello World")
val call = mock {
on { execute() } doReturn response
}
whenever(okHttpClient.newCall(any())).doReturn(call)
- val model1 = fakeModel(id = 1)
- val result = manager.performValidation(model1)
+ val model1 = fakeModel()
+ val result = manager.performCheck(model1)
assertThat(result.model).isEqualTo(
model1.copy(
- lastResult = model1.lastResult?.copy(
- status = ERROR,
- reason = "Response 500 - Hello World"
- )
+ status = ERROR,
+ reason = "Response 500 - Hello World"
)
)
}
- @Test fun performValidation_socketTimeout() = runBlocking {
+ @Test fun performCheck_socketTimeout() = runBlocking {
val error = SocketTimeoutException("Oh no!")
val call = mock {
on { execute() } doAnswer { throw error }
}
whenever(okHttpClient.newCall(any())).doReturn(call)
- val model1 = fakeModel(id = 1)
- val result = manager.performValidation(model1)
+ val model1 = fakeModel()
+ val result = manager.performCheck(model1)
assertThat(result.model).isEqualTo(
model1.copy(
- lastResult = model1.lastResult?.copy(
- status = ERROR,
- reason = timeoutError
- )
+ status = ERROR,
+ reason = timeoutError
)
)
}
- @Test fun performValidation_exception() = runBlocking {
+ @Test fun performCheck_exception() = runBlocking {
val error = Exception("Oh no!")
val call = mock {
on { execute() } doAnswer { throw error }
}
whenever(okHttpClient.newCall(any())).doReturn(call)
- val model1 = fakeModel(id = 1)
- val result = manager.performValidation(model1)
+ val model1 = fakeModel()
+ val result = manager.performCheck(model1)
assertThat(result.model).isEqualTo(
model1.copy(
- lastResult = model1.lastResult?.copy(
- status = ERROR,
- reason = "Oh no!"
- )
+ status = ERROR,
+ reason = "Oh no!"
)
)
}
- @Test fun performValidation_success_withHeaders() = runBlocking {
- val requestCaptor = argumentCaptor()
- val response = fakeResponse(200, "OK", "Hello World")
-
- val call = mock {
- on { execute() } doReturn response
- }
- whenever(okHttpClient.newCall(requestCaptor.capture()))
- .doReturn(call)
-
- val model1 = fakeModel(id = 1).copy(
- headers = listOf(
- Header(
- key = "X-Test-Header",
- value = "Hello, World!"
- )
- )
- )
- val result = manager.performValidation(model1)
- val httpRequest = requestCaptor.firstValue
-
- assertThat(result.model).isEqualTo(
- model1.copy(
- lastResult = model1.lastResult?.copy(
- status = OK,
- reason = null
- )
- )
- )
- assertThat(okHttpClient.callTimeoutMillis())
- .isEqualTo(model1.settings!!.networkTimeout)
- assertThat(httpRequest.header("X-Test-Header"))
- .isEqualTo("Hello, World!")
- }
-
- @Test fun performValidation_success_withCustomSslCert() = runBlocking {
+ @Test fun performCheck_success() = runBlocking {
val response = fakeResponse(200, "OK", "Hello World")
val call = mock {
on { execute() } doReturn response
}
whenever(okHttpClient.newCall(any())).doReturn(call)
- val model1 = fakeModel(id = 1).copy(
- url = "http://wwww.mysite.com/test.html",
- headers = emptyList()
- )
- model1.settings = model1.settings!!.copy(
- certificate = "file:///sdcard/cert.pem"
- )
- val result = manager.performValidation(model1)
+ val model1 = fakeModel()
+ val result = manager.performCheck(model1)
assertThat(result.model).isEqualTo(
model1.copy(
- lastResult = model1.lastResult?.copy(
- status = OK,
- reason = null
- )
+ status = OK,
+ reason = null
)
)
assertThat(okHttpClient.callTimeoutMillis())
- .isEqualTo(model1.settings!!.networkTimeout)
+ .isEqualTo(model1.networkTimeout)
+ }
- verify(sslManager).clientForCertificate(
- "file:///sdcard/cert.pem",
- "http://wwww.mysite.com/test.html",
- okHttpClient
+ @Test fun performCheck_401_butStillSuccess() = runBlocking {
+ val response = fakeResponse(401, "Unauthorized", "Hello World")
+ val call = mock {
+ on { execute() } doReturn response
+ }
+ whenever(okHttpClient.newCall(any())).doReturn(call)
+
+ val model1 = fakeModel()
+ val result = manager.performCheck(model1)
+
+ assertThat(result.model).isEqualTo(
+ model1.copy(
+ status = OK,
+ reason = null
+ )
)
}
@@ -337,6 +293,14 @@ class ValidationExecutorTest {
.build()
}
+ private fun fakeModel() = ServerModel(
+ id = 1,
+ name = "Wakanda Forever",
+ url = "https://www.wakanda.gov",
+ validationMode = STATUS_CODE,
+ networkTimeout = 60000
+ )
+
private fun fakeJob(id: Int): JobInfo {
return mock {
on { this.id } doReturn id
diff --git a/engine/src/test/java/com/afollestad/nocknock/engine/TestUtil.kt b/engine/src/test/kotlin/com/afollestad/nocknock/engine/TestUtil.kt
similarity index 74%
rename from engine/src/test/java/com/afollestad/nocknock/engine/TestUtil.kt
rename to engine/src/test/kotlin/com/afollestad/nocknock/engine/TestUtil.kt
index 7255e9f..af7113a 100644
--- a/engine/src/test/java/com/afollestad/nocknock/engine/TestUtil.kt
+++ b/engine/src/test/kotlin/com/afollestad/nocknock/engine/TestUtil.kt
@@ -18,8 +18,6 @@ package com.afollestad.nocknock.engine
import android.app.job.JobInfo
import android.content.ComponentName
import android.os.PersistableBundle
-import com.afollestad.nocknock.data.AppDatabase
-import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.utilities.providers.BundleProvider
import com.afollestad.nocknock.utilities.providers.IBundle
import com.afollestad.nocknock.utilities.providers.IBundler
@@ -36,11 +34,11 @@ fun testBundleProvider(): BundleProvider {
val realBundle = mock()
val creator = it.getArgument(0)
creator(object : IBundle {
- override fun putLong(
+ override fun putInt(
key: String,
- value: Long
+ value: Int
) {
- whenever(realBundle.getLong(key)).doReturn(value)
+ whenever(realBundle.getInt(key)).doReturn(value)
}
})
return@doAnswer realBundle
@@ -68,21 +66,3 @@ fun testJobInfoProvider(): JobInfoProvider {
}
return provider
}
-
-fun AppDatabase.setAllSites(vararg sites: Site) {
- whenever(siteDao().all()).doReturn(listOf(*sites))
- for (site in sites) {
- whenever(siteSettingsDao().forSite(site.id))
- .doReturn(listOf(site.settings!!))
- if (site.lastResult != null) {
- whenever(validationResultsDao().forSite(site.id))
- .doReturn(listOf(site.lastResult!!))
- }
- if (site.retryPolicy != null) {
- whenever(retryPolicyDao().forSite(site.id))
- .doReturn(listOf(site.retryPolicy!!))
- }
- whenever(headerDao().forSite(site.id))
- .doReturn(site.headers)
- }
-}
diff --git a/gradle.properties b/gradle.properties
index c62d03d..9516cf5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -16,6 +16,3 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
-
-android.useAndroidX=true
-android.enableJetifier=true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2128899..1c0396b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip
diff --git a/ic_web.png b/ic_web.png
index d54b6ec..509177f 100644
Binary files a/ic_web.png and b/ic_web.png differ
diff --git a/mock/mock-google-services.json b/mock/mock-google-services.json
deleted file mode 100644
index d9866d8..0000000
--- a/mock/mock-google-services.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "project_info": {
- "project_number": "123456789000",
- "firebase_url": "https://mockproject-1234.firebaseio.com",
- "project_id": "mockproject-1234",
- "storage_bucket": "mockproject-1234.appspot.com"
- },
- "client": [
- {
- "client_info": {
- "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063",
- "android_client_info": {
- "package_name": "com.afollestad.nocknock"
- }
- },
- "oauth_client": [
- {
- "client_id": "123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com",
- "client_type": 3
- }
- ],
- "api_key": [
- {
- "current_key": "AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo"
- }
- ],
- "services": {
- "analytics_service": {
- "status": 1
- },
- "appinvite_service": {
- "status": 1,
- "other_platform_oauth_client": []
- },
- "ads_service": {
- "status": 2
- }
- }
- }
- ],
- "configuration_version": "1"
-}
diff --git a/mock/mock.gradle b/mock/mock.gradle
deleted file mode 100644
index 2fa58ef..0000000
--- a/mock/mock.gradle
+++ /dev/null
@@ -1,13 +0,0 @@
-// This script must be applied in app/build.gradle for the paths here to work correctly
-
-def copyMockFilesNeeded() {
- def srcGoogleServicesFile = file("../mock/mock-google-services.json")
- def destGoogleServicesFile = file("google-services.json")
- if (!destGoogleServicesFile.exists()) {
- destGoogleServicesFile.write(srcGoogleServicesFile.text)
- }
-}
-
-afterEvaluate {
- copyMockFilesNeeded()
-}
\ No newline at end of file
diff --git a/notifications/src/main/java/com/afollestad/nocknock/notifications/Channel.kt b/notifications/src/main/java/com/afollestad/nocknock/notifications/Channel.kt
index 7cad6ca..dcf9f76 100644
--- a/notifications/src/main/java/com/afollestad/nocknock/notifications/Channel.kt
+++ b/notifications/src/main/java/com/afollestad/nocknock/notifications/Channel.kt
@@ -24,13 +24,7 @@ enum class Channel(
val description: Int,
val importance: Int
) {
- ValidationSuccess(
- id = "check_success",
- title = R.string.channel_server_check_success_title,
- description = R.string.channel_server_check_success_description,
- importance = IMPORTANCE_DEFAULT
- ),
- ValidationErrors(
+ CheckFailures(
id = "check_failures",
title = R.string.channel_server_check_failures_title,
description = R.string.channel_server_check_failures_description,
diff --git a/notifications/src/main/java/com/afollestad/nocknock/notifications/NockNotificationManager.kt b/notifications/src/main/java/com/afollestad/nocknock/notifications/NockNotificationManager.kt
index 84addbc..e497151 100644
--- a/notifications/src/main/java/com/afollestad/nocknock/notifications/NockNotificationManager.kt
+++ b/notifications/src/main/java/com/afollestad/nocknock/notifications/NockNotificationManager.kt
@@ -18,7 +18,7 @@ package com.afollestad.nocknock.notifications
import android.annotation.TargetApi
import android.app.NotificationManager
import android.os.Build.VERSION_CODES
-import com.afollestad.nocknock.notifications.Channel.ValidationErrors
+import com.afollestad.nocknock.notifications.Channel.CheckFailures
import com.afollestad.nocknock.utilities.providers.CanNotifyModel
import com.afollestad.nocknock.utilities.providers.IntentProvider
import com.afollestad.nocknock.utilities.providers.NotificationChannelProvider
@@ -34,9 +34,7 @@ interface NockNotificationManager {
fun createChannels()
- fun postValidationErrorNotification(model: CanNotifyModel)
-
- fun postValidationSuccessNotification(model: CanNotifyModel)
+ fun postStatusNotification(model: CanNotifyModel)
fun cancelStatusNotification(model: CanNotifyModel)
@@ -62,48 +60,26 @@ class RealNockNotificationManager(
override fun createChannels() =
Channel.values().forEach(this::createChannel)
- override fun postValidationErrorNotification(model: CanNotifyModel) {
+ override fun postStatusNotification(model: CanNotifyModel) {
if (isAppOpen) {
// Don't show notifications while the app is open
- log("App is open, validation error notification for site ${model.notifyId()} won't be posted.")
+ log("App is open, status notification for site ${model.notifyId()} won't be posted.")
return
}
- log("Posting validation error notification for site ${model.notifyId()}...")
+ log("Posting status notification for site ${model.notifyId()}...")
val intent = intentProvider.getPendingIntentForViewSite(model)
val newNotification = notificationProvider.create(
- channelId = ValidationErrors.id,
+ channelId = CheckFailures.id,
title = model.notifyName(),
- content = model.notifyDescription() ?: stringProvider.get(R.string.something_wrong),
+ content = stringProvider.get(R.string.something_wrong),
intent = intent,
- smallIcon = R.drawable.ic_notification_error
+ smallIcon = R.drawable.ic_notification
)
stockManager.notify(model.notifyTag(), model.notificationId(), newNotification)
- log("Posted validation error notification for site ${model.notificationId()}.")
- }
-
- override fun postValidationSuccessNotification(model: CanNotifyModel) {
- if (isAppOpen) {
- // Don't show notifications while the app is open
- log("App is open, validation success notification for site ${model.notifyId()} won't be posted.")
- return
- }
-
- log("Posting validation success notification for site ${model.notifyId()}...")
- val intent = intentProvider.getPendingIntentForViewSite(model)
-
- val newNotification = notificationProvider.create(
- channelId = ValidationErrors.id,
- title = model.notifyName(),
- content = stringProvider.get(R.string.validation_passed),
- intent = intent,
- smallIcon = R.drawable.ic_notification_success
- )
-
- stockManager.notify(model.notifyTag(), model.notificationId(), newNotification)
- log("Posted validation success notification for site ${model.notificationId()}.")
+ log("Posted status notification for site ${model.notificationId()}.")
}
override fun cancelStatusNotification(model: CanNotifyModel) {
diff --git a/notifications/src/main/res/drawable/ic_notification_error.xml b/notifications/src/main/res/drawable/ic_notification.xml
similarity index 100%
rename from notifications/src/main/res/drawable/ic_notification_error.xml
rename to notifications/src/main/res/drawable/ic_notification.xml
diff --git a/notifications/src/main/res/drawable/ic_notification_success.xml b/notifications/src/main/res/drawable/ic_notification_success.xml
deleted file mode 100644
index 290fb76..0000000
--- a/notifications/src/main/res/drawable/ic_notification_success.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/notifications/src/main/res/values/strings.xml b/notifications/src/main/res/values/strings.xml
index 4462309..ba78f43 100644
--- a/notifications/src/main/res/values/strings.xml
+++ b/notifications/src/main/res/values/strings.xml
@@ -1,18 +1,12 @@
- Site Validation Failures
+ Server Check Failures
- Notifications for Nock Nock validations failing for your sites. Something has gone
+ Notifications for Nock Nock status checks failing for your sites. Something has gone
wrong if you see one of these.
- Site Validation Success
-
- Notifications for Nock Nock when a site validation passes when it previously had not.
-
-
Something\'s wrong! Tap for details.
- Yay! No longer in trouble! Validation passed.
diff --git a/notifications/src/test/java/com/afollestad/nocknock/notifications/NockNotificationManagerTest.kt b/notifications/src/test/kotlin/com/afollestad/nocknock/notifications/NockNotificationManagerTest.kt
similarity index 75%
rename from notifications/src/test/java/com/afollestad/nocknock/notifications/NockNotificationManagerTest.kt
rename to notifications/src/test/kotlin/com/afollestad/nocknock/notifications/NockNotificationManagerTest.kt
index d4709ab..0f92bc0 100644
--- a/notifications/src/test/java/com/afollestad/nocknock/notifications/NockNotificationManagerTest.kt
+++ b/notifications/src/test/kotlin/com/afollestad/nocknock/notifications/NockNotificationManagerTest.kt
@@ -19,8 +19,7 @@ import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
-import com.afollestad.nocknock.notifications.Channel.ValidationErrors
-import com.afollestad.nocknock.notifications.Channel.ValidationSuccess
+import com.afollestad.nocknock.notifications.Channel.CheckFailures
import com.afollestad.nocknock.utilities.providers.CanNotifyModel
import com.afollestad.nocknock.utilities.providers.IntentProvider
import com.afollestad.nocknock.utilities.providers.NotificationChannelProvider
@@ -42,13 +41,12 @@ import org.junit.Test
class NockNotificationManagerTest {
+ private val appIconRes = 1024
private val somethingWentWrong = "something went wrong"
- private val yay = "yay!"
private val stockManager = mock()
private val stringProvider = mock {
on { get(R.string.something_wrong) } doReturn somethingWentWrong
- on { get(R.string.validation_passed) } doReturn yay
}
private val intentProvider = mock()
private val channelProvider = mock()
@@ -79,42 +77,30 @@ class NockNotificationManagerTest {
@Test fun createChannels() {
whenever(stringProvider.get(any())).doReturn("")
- val errorChannel = mock {
- on { this.id } doReturn ValidationErrors.id
- }
- val successChannel = mock {
- on { this.id } doReturn ValidationSuccess.id
- }
-
- whenever(channelProvider.create(any(), any(), any(), any())).doAnswer { inv ->
- val channelId = inv.getArgument(0)
- when (channelId) {
- ValidationErrors.id -> errorChannel
- ValidationSuccess.id -> successChannel
- else -> null
- }
+ val createdChannel = mock {
+ on { this.id } doReturn CheckFailures.id
}
+ whenever(channelProvider.create(any(), any(), any(), any()))
+ .doReturn(createdChannel)
manager.createChannels()
val captor = argumentCaptor()
- verify(stockManager, times(2)).createNotificationChannel(captor.capture())
+ verify(stockManager, times(1)).createNotificationChannel(captor.capture())
- val channels = captor.allValues
- assertThat(channels.size).isEqualTo(2)
- assertThat(channels.first()).isEqualTo(successChannel)
- assertThat(channels.last()).isEqualTo(errorChannel)
+ val channel = captor.allValues.single()
+ assertThat(channel.id).isEqualTo(CheckFailures.id)
verifyNoMoreInteractions(stockManager)
}
- @Test fun postValidationSuccessNotification_appIsOpen() {
+ @Test fun postStatusNotification_appIsOpen() {
manager.setIsAppOpen(true)
- manager.postValidationSuccessNotification(fakeModel())
+ manager.postStatusNotification(fakeModel())
verifyNoMoreInteractions(stockManager)
}
- @Test fun postValidationSuccessNotification_appNotOpen() {
+ @Test fun postStatusNotification_appNotOpen() {
manager.setIsAppOpen(false)
val model = fakeModel()
@@ -125,15 +111,15 @@ class NockNotificationManagerTest {
val notification = mock()
whenever(
notificationProvider.create(
- ValidationErrors.id,
+ CheckFailures.id,
"Testing",
- yay,
+ somethingWentWrong,
pendingIntent,
- R.drawable.ic_notification_success
+ R.drawable.ic_notification
)
).doReturn(notification)
- manager.postValidationSuccessNotification(model)
+ manager.postStatusNotification(model)
verify(stockManager).notify(
"https://hello.com",
@@ -143,13 +129,6 @@ class NockNotificationManagerTest {
verifyNoMoreInteractions(stockManager)
}
- @Test fun postValidationErrorNotification_appIsOpen() {
- manager.setIsAppOpen(true)
- manager.postValidationErrorNotification(fakeModel())
-
- verifyNoMoreInteractions(stockManager)
- }
-
@Test fun cancelStatusNotification() {
val model = fakeModel()
manager.cancelStatusNotification(model)
@@ -169,7 +148,5 @@ class NockNotificationManagerTest {
override fun notifyName() = "Testing"
override fun notifyTag() = "https://hello.com"
-
- override fun notifyDescription() = "Hello, World!"
}
}
diff --git a/viewcomponents/build.gradle b/viewcomponents/build.gradle
index a3ae3dc..abbff09 100644
--- a/viewcomponents/build.gradle
+++ b/viewcomponents/build.gradle
@@ -18,10 +18,7 @@ dependencies {
implementation project(':common')
implementation project(':data')
- api 'com.afollestad:vvalidator:' + versions.vvalidator
-
implementation 'androidx.appcompat:appcompat:' + versions.androidxCore
- implementation 'com.google.android.material:material:' + versions.googleMaterial
api 'androidx.lifecycle:lifecycle-extensions:' + versions.lifecycle
api 'com.squareup.okhttp3:okhttp:' + versions.okHttp
diff --git a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/ext/ViewExt.kt b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/ext/ViewExt.kt
index e543863..feeef85 100644
--- a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/ext/ViewExt.kt
+++ b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/ext/ViewExt.kt
@@ -17,24 +17,37 @@ package com.afollestad.nocknock.viewcomponents.ext
import android.view.View
import android.view.View.GONE
+import android.view.View.INVISIBLE
import android.view.View.VISIBLE
+import android.view.ViewTreeObserver
import androidx.annotation.DimenRes
-import com.afollestad.vvalidator.form.Condition
fun View.show() {
visibility = VISIBLE
}
+fun View.conceal() {
+ visibility = INVISIBLE
+}
+
fun View.hide() {
visibility = GONE
}
fun View.showOrHide(show: Boolean) = if (show) show() else hide()
+fun View.onLayout(cb: () -> Unit) {
+ if (this.viewTreeObserver.isAlive) {
+ this.viewTreeObserver.addOnGlobalLayoutListener(
+ object : ViewTreeObserver.OnGlobalLayoutListener {
+ override fun onGlobalLayout() {
+ cb()
+ this@onLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
+ }
+ })
+ }
+}
+
fun View.dimenFloat(@DimenRes res: Int) = resources.getDimension(res)
fun View.dimenInt(@DimenRes res: Int) = resources.getDimensionPixelSize(res)
-
-fun View.isVisibleCondition(): Condition = {
- visibility == VISIBLE
-}
diff --git a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/headers/HeaderItemLayout.kt b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/headers/HeaderItemLayout.kt
deleted file mode 100644
index 4163bbc..0000000
--- a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/headers/HeaderItemLayout.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * Designed and developed by Aidan Follestad (@afollestad)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.afollestad.nocknock.viewcomponents.headers
-
-import android.content.Context
-import android.util.AttributeSet
-import android.widget.LinearLayout
-import com.afollestad.nocknock.data.model.Header
-import com.afollestad.nocknock.utilities.ext.onTextChanged
-import com.afollestad.nocknock.viewcomponents.R
-import kotlinx.android.synthetic.main.header_stack_item_content.view.inputKey
-import kotlinx.android.synthetic.main.header_stack_item_content.view.inputValue
-
-/** @author Aidan Follestad (@afollestad) */
-class HeaderItemLayout(
- context: Context,
- attrs: AttributeSet? = null
-) : LinearLayout(context, attrs) {
-
- private var header: Header? = null
- private var stack: HeaderStackLayout? = null
-
- init {
- z
- orientation = HORIZONTAL
- inflate(context, R.layout.header_stack_item_content, this)
- }
-
- fun attachHeader(
- newHeader: Header,
- parentStack: HeaderStackLayout
- ) {
- this.header = newHeader
- this.stack = parentStack
-
- inputKey.run {
- setText(newHeader.key)
- onTextChanged {
- header?.key = it.trim()
- stack?.postLiveData()
- }
- }
-
- inputValue.run {
- setText(newHeader.value)
- onTextChanged {
- header?.value = it.trim()
- stack?.postLiveData()
- }
- }
- }
-}
diff --git a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/headers/HeaderStackLayout.kt b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/headers/HeaderStackLayout.kt
deleted file mode 100644
index 6abff33..0000000
--- a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/headers/HeaderStackLayout.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * Designed and developed by Aidan Follestad (@afollestad)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.afollestad.nocknock.viewcomponents.headers
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.OnClickListener
-import android.widget.LinearLayout
-import androidx.lifecycle.MutableLiveData
-import com.afollestad.nocknock.data.model.Header
-import com.afollestad.nocknock.viewcomponents.R
-import kotlinx.android.synthetic.main.header_stack_item_content.view.btnRemove
-import kotlinx.android.synthetic.main.header_stack_item_content.view.inputKey
-import kotlinx.android.synthetic.main.header_stack_item_content.view.inputValue
-import kotlinx.android.synthetic.main.header_stack_layout.view.addHeader
-import kotlinx.android.synthetic.main.header_stack_layout.view.header_list as list
-
-/** @author Aidan Follestad (@afollestad) */
-class HeaderStackLayout(
- context: Context,
- attrs: AttributeSet? = null
-) : LinearLayout(context, attrs), OnClickListener {
-
- private var data: MutableLiveData>? = null
- private var headers = mutableListOf()
-
- init {
- orientation = VERTICAL
- inflate(context, R.layout.header_stack_layout, this)
- addHeader.setOnClickListener { addEntry(Header()) }
- }
-
- fun attach(data: MutableLiveData>) {
- list.removeAllViews()
- headers.clear()
- data.value?.forEach(::addEntry)
- this.data = data
- }
-
- fun postLiveData() = this.data?.postValue(headers)
-
- override fun onClick(v: View) {
- val index = v.tag as Int
- check(index >= 0 || index < list.childCount) {
- "Index $index is out of bounds in the header stack (size ${list.childCount})."
- }
- list.post {
- list.removeViewAt(index)
- headers.removeAt(index)
- invalidateTags()
- postLiveData()
- }
- }
-
- private fun invalidateTags() {
- for (i in 0 until list.childCount) {
- val entry = list.getChildAt(i) as HeaderItemLayout
- entry.btnRemove.tag = i
- }
- }
-
- private fun addEntry(forHeader: Header) {
- // Keep track of reference for posting future changes.
- headers.add(forHeader)
-
- val li = LayoutInflater.from(context)
- val entry = li.inflate(R.layout.header_stack_item, list, false) as HeaderItemLayout
- list.addView(entry.apply {
- inputKey.setText(forHeader.key)
- inputKey.post { entry.inputKey.requestFocus() }
- attachHeader(forHeader, this@HeaderStackLayout)
- inputValue.setText(forHeader.value)
-
- btnRemove.tag = headers.size - 1
- btnRemove.setOnClickListener(this@HeaderStackLayout)
- })
- }
-}
diff --git a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/interval/ValidationIntervalLayout.kt b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/interval/ValidationIntervalLayout.kt
index 2db60fe..58bbe9d 100644
--- a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/interval/ValidationIntervalLayout.kt
+++ b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/interval/ValidationIntervalLayout.kt
@@ -19,15 +19,17 @@ import android.content.Context
import android.util.AttributeSet
import android.widget.ArrayAdapter
import android.widget.LinearLayout
+import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
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
+import com.afollestad.nocknock.viewcomponents.R.array
+import com.afollestad.nocknock.viewcomponents.R.layout
import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData
import com.afollestad.nocknock.viewcomponents.livedata.lifecycleOwner
-import com.afollestad.vvalidator.form.Form
+import com.afollestad.nocknock.viewcomponents.livedata.toViewError
import kotlinx.android.synthetic.main.validation_interval_layout.view.input
import kotlinx.android.synthetic.main.validation_interval_layout.view.spinner
@@ -46,18 +48,18 @@ class ValidationIntervalLayout(
init {
orientation = VERTICAL
- inflate(context, R.layout.validation_interval_layout, this)
+ inflate(context, layout.validation_interval_layout, this)
}
override fun onFinishInflate() {
super.onFinishInflate()
val spinnerAdapter = ArrayAdapter(
context,
- R.layout.list_item_spinner,
- resources.getStringArray(R.array.interval_options)
+ layout.list_item_spinner,
+ resources.getStringArray(array.interval_options)
)
spinnerAdapter.setDropDownViewResource(
- R.layout.list_item_spinner_dropdown
+ layout.list_item_spinner_dropdown
)
spinner.adapter = spinnerAdapter
}
@@ -65,14 +67,8 @@ class ValidationIntervalLayout(
fun attach(
valueData: MutableLiveData,
multiplierData: MutableLiveData,
- form: Form
+ errorData: LiveData
) {
- form.input(input, name = "Interval") {
- isNotEmpty().description(R.string.please_enter_check_interval)
- length().greaterThan(0)
- .description(R.string.check_interval_must_be_greater_zero)
- }
-
input.attachLiveData(lifecycleOwner(), valueData)
spinner.attachLiveData(
lifecycleOwner = lifecycleOwner(),
@@ -96,5 +92,10 @@ class ValidationIntervalLayout(
}
}
)
+ errorData.toViewError(lifecycleOwner(), this, ::setError)
+ }
+
+ private fun setError(error: String?) {
+ input.error = error
}
}
diff --git a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/js/JavaScriptInputLayout.kt b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/js/JavaScriptInputLayout.kt
index 1b1a29f..5d32e69 100644
--- a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/js/JavaScriptInputLayout.kt
+++ b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/js/JavaScriptInputLayout.kt
@@ -20,17 +20,15 @@ import android.util.AttributeSet
import android.widget.HorizontalScrollView
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
-import com.afollestad.nocknock.viewcomponents.R
import com.afollestad.nocknock.viewcomponents.R.dimen
import com.afollestad.nocknock.viewcomponents.R.layout
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
import com.afollestad.nocknock.viewcomponents.ext.dimenInt
-import com.afollestad.nocknock.viewcomponents.ext.isVisibleCondition
import com.afollestad.nocknock.viewcomponents.ext.showOrHide
import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData
import com.afollestad.nocknock.viewcomponents.livedata.lifecycleOwner
+import com.afollestad.nocknock.viewcomponents.livedata.toViewError
import com.afollestad.nocknock.viewcomponents.livedata.toViewVisibility
-import com.afollestad.vvalidator.form.Form
import kotlinx.android.synthetic.main.javascript_input_layout.view.error_text
import kotlinx.android.synthetic.main.javascript_input_layout.view.userInput
@@ -52,25 +50,16 @@ class JavaScriptInputLayout(
contentInset // bottom
)
elevation = dimenFloat(dimen.default_elevation)
- inflate(context, R.layout.javascript_input_layout, this)
+ inflate(context, layout.javascript_input_layout, this)
}
fun attach(
codeData: MutableLiveData,
- visibility: LiveData,
- form: Form
+ errorData: LiveData,
+ visibility: LiveData
) {
- form.input(userInput, name = "Script") {
- conditional(isVisibleCondition()) {
- isNotEmpty().description(R.string.please_enter_javaScript)
- }
- onErrors { _, errors ->
- val error = errors.firstOrNull()
- setError(error.toString())
- }
- }
-
userInput.attachLiveData(lifecycleOwner(), codeData)
+ errorData.toViewError(lifecycleOwner(), this, ::setError)
visibility.toViewVisibility(lifecycleOwner(), this)
}
diff --git a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/retrypolicy/RetryPolicyLayout.kt b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/retrypolicy/RetryPolicyLayout.kt
index 5a9375d..95e84d0 100644
--- a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/retrypolicy/RetryPolicyLayout.kt
+++ b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/retrypolicy/RetryPolicyLayout.kt
@@ -24,7 +24,6 @@ import com.afollestad.nocknock.viewcomponents.R
import com.afollestad.nocknock.viewcomponents.ext.asSafeInt
import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData
import com.afollestad.nocknock.viewcomponents.livedata.lifecycleOwner
-import com.afollestad.vvalidator.form.Form
import kotlinx.android.synthetic.main.retry_policy_layout.view.minutes
import kotlinx.android.synthetic.main.retry_policy_layout.view.times
import kotlinx.android.synthetic.main.retry_policy_layout.view.retry_policy_desc as description
@@ -42,8 +41,7 @@ class RetryPolicyLayout(
fun attach(
timesData: MutableLiveData,
- minutesData: MutableLiveData,
- form: Form
+ minutesData: MutableLiveData
) {
times.attachLiveData(lifecycleOwner(), timesData)
minutes.attachLiveData(lifecycleOwner(), minutesData)
@@ -52,13 +50,6 @@ class RetryPolicyLayout(
minutes.onTextChanged { invalidateDescriptionText() }
invalidateDescriptionText()
-
- form.input(times, optional = true) {
- isNumber().greaterThan(0)
- }
- form.input(minutes, optional = true) {
- isNumber().greaterThan(0)
- }
}
private fun invalidateDescriptionText() {
diff --git a/viewcomponents/src/main/res/drawable/ic_chevron_right.xml b/viewcomponents/src/main/res/drawable/ic_chevron_right.xml
deleted file mode 100644
index 3483bbb..0000000
--- a/viewcomponents/src/main/res/drawable/ic_chevron_right.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/viewcomponents/src/main/res/drawable/ic_close.xml b/viewcomponents/src/main/res/drawable/ic_close.xml
deleted file mode 100644
index 6c28089..0000000
--- a/viewcomponents/src/main/res/drawable/ic_close.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/viewcomponents/src/main/res/layout/header_stack_item.xml b/viewcomponents/src/main/res/layout/header_stack_item.xml
deleted file mode 100644
index c87066f..0000000
--- a/viewcomponents/src/main/res/layout/header_stack_item.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
diff --git a/viewcomponents/src/main/res/layout/header_stack_item_content.xml b/viewcomponents/src/main/res/layout/header_stack_item_content.xml
deleted file mode 100644
index d963ccc..0000000
--- a/viewcomponents/src/main/res/layout/header_stack_item_content.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/viewcomponents/src/main/res/layout/header_stack_layout.xml b/viewcomponents/src/main/res/layout/header_stack_layout.xml
deleted file mode 100644
index e497e2a..0000000
--- a/viewcomponents/src/main/res/layout/header_stack_layout.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/viewcomponents/src/main/res/layout/validation_interval_layout.xml b/viewcomponents/src/main/res/layout/validation_interval_layout.xml
index af4592c..2a16de4 100644
--- a/viewcomponents/src/main/res/layout/validation_interval_layout.xml
+++ b/viewcomponents/src/main/res/layout/validation_interval_layout.xml
@@ -12,32 +12,24 @@
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/validation_interval"
+ android:text="@string/check_interval"
style="@style/NockText.SectionHeader"
/>
-
-
function validate(response) {
}
- Validation Interval
- Every
+ Check Interval
Retry Policy
Retry
@@ -17,14 +16,4 @@
values. After retrying %1$d times over %2$d minutes with no success, you will get a notification.
- Headers
- Add Header…
- Add HTTP headers to each request made to validate this site.
- Header Name
- Header Value
-
- Please input a validation interval.
- The validation interval must be greater than 0.
- Please input a validation script.
-
diff --git a/viewcomponents/src/main/res/values/styles.xml b/viewcomponents/src/main/res/values/styles.xml
index 838adb7..f3be3fb 100644
--- a/viewcomponents/src/main/res/values/styles.xml
+++ b/viewcomponents/src/main/res/values/styles.xml
@@ -9,12 +9,6 @@
- ?colorAccent
-
-
-
-
-
-