Compare commits

..

No commits in common. "master" and "0.8.6" have entirely different histories.

54 changed files with 173 additions and 286 deletions

4
.gitignore vendored
View file

@ -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
# gradle/wrapper/gradle-wrapper.properties

22
.travis.yml Normal file
View file

@ -0,0 +1,22 @@
language: android
jdk: oraclejdk8
env:
matrix:
- ANDROID_TARGET=android-21 ANDROID_ABI=armeabi-v7a
android:
components:
- tools # to get the new `repository-11.xml`
- tools # see https://github.com/travis-ci/travis-ci/issues/6040#issuecomment-219367943)
- platform-tools
- build-tools-28.0.3
- android-28
licenses:
- '.+'
before_install:
- yes | sdkmanager "platforms;android-28"
script:
- ./gradlew build connectedCheck

View file

@ -1,5 +1,6 @@
## Nock Nock
[![Build Status](https://travis-ci.org/afollestad/nock-nock.svg)](https://travis-ci.org/afollestad/nock-nock)
[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg?style=flat-square)](https://www.apache.org/licenses/LICENSE-2.0.html)
![Showcase](https://raw.githubusercontent.com/afollestad/nock-nock/master/art/showcase5.png)

View file

@ -4,6 +4,8 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply from: '../fabric.gradle'
android {
compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools
@ -16,13 +18,14 @@ android {
versionName versions.publishVersion
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
packagingOptions {
exclude 'META-INF/atomicfu.kotlin_module'
buildTypes {
debug {
ext.enableCrashlytics = false
buildConfigField "String", "FABRIC_API_KEY", "\"\""
}
release {
buildConfigField "String", "FABRIC_API_KEY", "\"${getFabricApiKey()}\""
}
}
}
@ -38,7 +41,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 +74,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'
apply from: '../spotless.gradle'

View file

@ -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,

View file

@ -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())

View file

@ -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
}

View file

@ -24,6 +24,7 @@ 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
@ -32,8 +33,10 @@ 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
@ -90,17 +93,12 @@ 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
}
@ -146,4 +144,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)
}
}
}

View file

@ -223,7 +223,7 @@ class ViewSiteViewModel(
private fun getUpdatedDbModel(): Site? {
val timeout = timeout.value ?: 10_000
val cleanedTags = tags.value?.split(',')?.joinToString(separator = ",") ?: ""
val cleanedTags = tags.value?.split(',')?.joinToString { it.trim() } ?: ""
val newSettings = site.settings!!.copy(
validationIntervalMs = getCheckIntervalMs(),

View file

@ -123,7 +123,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset"
android:layout_marginTop="@dimen/content_inset_half"
android:background="?scriptLayoutBackground"
android:background="@color/lighterGray"
/>
<TextView

View file

@ -161,7 +161,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset"
android:layout_marginTop="@dimen/content_inset_half"
android:background="?scriptLayoutBackground"
android:background="@color/lighterGray"
/>
<TextView

View file

@ -7,4 +7,7 @@
android:id="@+id/dark_mode"
android:checkable="true"
android:title="@string/dark_mode"/>
<item
android:id="@+id/support_me"
android:title="@string/support_me"/>
</menu>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View file

@ -13,4 +13,10 @@
<item>JavaScript Evaluation</item>
</string-array>
<string-array name="donation_options">
<item>via PayPal</item>
<item>via Cash App</item>
<item>via Venmo</item>
</string-array>
</resources>

View file

@ -4,6 +4,5 @@
<attr format="color" name="toolbarTitleColor"/>
<attr format="color" name="dividerColor"/>
<attr format="color" name="iconColor"/>
<attr format="color" name="scriptLayoutBackground"/>
</resources>

View file

@ -7,9 +7,7 @@
<color name="colorPrimary_darkTheme">#212121</color>
<color name="colorPrimaryDark_darkTheme">#252525</color>
<color name="darkerGray">#303030</color>
<color name="lighterGray">#EEEEEE</color>
<color name="lighterGray">#303030</color>
<color name="colorAccent">#FF6E40</color>
<color name="colorAccent_pressed">#E44615</color>
<color name="colorAccent_translucent">#40FF6E40</color>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#758F9A</color>
</resources>

View file

@ -14,7 +14,6 @@
<a href=\'https://www.linkedin.com/in/afollestad\'>LinkedIn</a>
<br/><br/><i>Nock Nock is open source! Check out the <a href=\'https://github.com/afollestad/nock-nock\'>GitHub page</a>!</i>
<br/>Icon by <a href=\'https://plus.google.com/+KevinAguilarC\'>Kevin Aguilar</a> of <b>221 Pixels</b>.
<br/>View the <a href=\'https://af.codes/privacypolicies/nocknock.html\'>Privacy Policy</a>.
]]></string>
<string name="dark_mode">Dark Mode</string>
@ -85,6 +84,14 @@
exception to pass custom error messages to Nock Nock.
</string>
<string name="support_me">Donate</string>
<string name="support_me_message"><![CDATA[
<b>Nock Nock</b> was created and is maintained by one person. Donations are <b>much</b>
appreciated and encourage continued support.
]]></string>
<string name="thank_you">Thank you very much!</string>
<string name="next">Next</string>
<string name="install_web_browser">Please install a web browser app, such as Google Chrome.</string>
</resources>

View file

@ -6,7 +6,7 @@
<style name="PrimaryDarkButton" parent="Widget.MaterialComponents.Button">
<item name="android:textColor">#fff</item>
<item name="backgroundTint">@color/darkerGray</item>
<item name="backgroundTint">@color/lighterGray</item>
<item name="android:fontFamily">@font/lato</item>
</style>

View file

@ -9,7 +9,6 @@
<item name="toolbarTitleColor">#000000</item>
<item name="dividerColor">#EEEEEE</item>
<item name="iconColor">#000000</item>
<item name="scriptLayoutBackground">@color/lighterGray</item>
<item name="android:textColorPrimary">#212121</item>
<item name="android:textColorSecondary">#727272</item>
@ -34,7 +33,6 @@
<item name="toolbarTitleColor">#ffffff</item>
<item name="dividerColor">#303030</item>
<item name="iconColor">#FFFFFF</item>
<item name="scriptLayoutBackground">@color/darkerGray</item>
<item name="android:textColorPrimary">#FFFFFF</item>
<item name="android:textColorSecondary">#F0F0F0</item>

View file

@ -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.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
@ -160,24 +159,19 @@ class AddSiteViewModelTest {
val siteCaptor = argumentCaptor<Site>()
val settingsCaptor = argumentCaptor<SiteSettings>()
val validationResultCaptor = argumentCaptor<ValidationResult>()
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(
site = model,
rightNow = true,
@ -197,10 +191,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")
)
}
}

View file

@ -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
@ -31,7 +29,6 @@ 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.mockDatabase
import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.utilities.livedata.test
@ -41,10 +38,8 @@ 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.verify
import com.nhaarman.mockitokotlin2.whenever
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
@ -260,8 +255,6 @@ class ViewSiteViewModelTest {
}
@Test fun commit_success() = runBlocking {
whenever(database.retryPolicyDao().forSite(any())).doReturn(listOf(fakeRetryPolicy(1)))
val isLoading = viewModel.onIsLoading()
.test()
@ -274,13 +267,11 @@ class ViewSiteViewModelTest {
val siteCaptor = argumentCaptor<Site>()
val settingsCaptor = argumentCaptor<SiteSettings>()
val resultCaptor = argumentCaptor<ValidationResult>()
val retryPolicyCaptor = argumentCaptor<RetryPolicy>()
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,13 +284,11 @@ 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)
@ -384,12 +373,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")
)
}
}

View file

@ -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" }
}

View file

@ -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

View file

@ -14,10 +14,6 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
packagingOptions {
exclude 'META-INF/atomicfu.kotlin_module'
}
}
dependencies {

View file

@ -431,30 +431,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() {
@ -491,12 +470,10 @@ class AppDatabaseTest() {
)
val updatedHeaders = listOf(
modelToUpdate.headers.first().copy(
id = 7,
key = "One",
value = "Hello"
),
modelToUpdate.headers.last().copy(
id = 8,
key = "Two",
value = "Hey"
)

View file

@ -35,8 +35,7 @@ fun fakeSettingsModel(
validationMode = validationMode,
validationArgs = null,
disabled = false,
networkTimeout = 10000,
certificate = null
networkTimeout = 10000
)
fun fakeResultModel(

View file

@ -3,56 +3,53 @@ ext.versions = [
minSdk : 21,
compileSdk : 28,
buildTools : '28.0.3',
publishVersion : '0.8.8',
publishVersionCode : 46,
publishVersion : '0.8.6',
publishVersionCode : 40,
// Plugins
gradlePlugin : '3.4.0',
spotlessPlugin : '3.22.0',
versionPlugin : '0.21.0',
googleServices : '4.2.0',
gradlePlugin : '3.3.0',
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.20',
coroutines : '1.1.1',
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-rc9',
rxkPrefs : '1.2.2',
vvalidator : '0.2.2',
// Debugging
timber : '4.7.1',
fabric : '2.9.9@aar',
fabric : '2.9.8@aar',
// Unit testing
junit : '4.12',
mockito : '2.27.0',
mockito : '2.23.4',
mockitoKotlin : '2.1.0',
truth : '0.44',
truth : '0.42',
// UI testing
androidxTestRunner : '1.1.1',
androidxTest : '1.1.0',
archTesting : '2.0.1'
archTesting : '2.0.0'
]

View file

@ -36,7 +36,6 @@ 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) */
@ -155,23 +154,21 @@ class RealValidationExecutor(
check(site.id != 0L) { "Cannot schedule validations for jobs with no ID." }
val siteSettings = site.settings
requireNotNull(siteSettings) { "Site settings must be populated." }
check(siteSettings.networkTimeout > 0) { "Network timeout not set for site ${site.id}" }
log("performValidation(${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)
}
site.headers.forEach { header ->
addHeader(header.key, header.value)
}
}
.build()
return try {
val timeout = max(siteSettings.networkTimeout, 1)
val clientWithTimeout = clientTimeoutChanger(okHttpClient, timeout)
val clientWithTimeout = clientTimeoutChanger(okHttpClient, siteSettings.networkTimeout)
val client = if (siteSettings.certificate.isNotNullOrEmpty()) {
sslManager.clientForCertificate(
certUri = siteSettings.certificate!!,

29
fabric.gradle Normal file
View file

@ -0,0 +1,29 @@
apply plugin: 'io.fabric'
ext.getFabricApiKey = {
return System.getenv('FABRIC_APIKEY') ?: "xxxe76c4xxxx97e8cxxxx0135e9d46f5a2xxx"
}
ext.getFabricApiSecret = {
return System.getenv('FABRIC_APISECRET') ?: "xx68f6074dxxxxxc11dxxx97c172e8ebf0"
}
def buildFabricProperties() {
def propertiesFile = file("fabric.properties")
def apiSecret = getFabricApiSecret()
def apiKey = getFabricApiKey()
if (propertiesFile.exists()) {
propertiesFile.delete()
}
def commentMessage = "suppress inspection \"UnusedProperty\" for whole file"
ant.propertyfile(file: "fabric.properties", comment: commentMessage) {
entry(key: "apiSecret", value: apiSecret)
entry(key: "apiKey", value: apiKey)
}
}
afterEvaluate {
buildFabricProperties()
}

View file

@ -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

View file

@ -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.1-bin.zip

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -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"
}

View file

@ -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()
}

View file

@ -18,6 +18,7 @@ package com.afollestad.nocknock.viewcomponents.ext
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewTreeObserver
import androidx.annotation.DimenRes
import com.afollestad.vvalidator.form.Condition
@ -31,6 +32,18 @@ fun View.hide() {
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)

View file

@ -56,22 +56,9 @@ class HeaderStackLayout(
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
}
list.removeViewAt(index)
headers.removeAt(index)
postLiveData()
}
private fun addEntry(forHeader: Header) {
@ -80,7 +67,9 @@ class HeaderStackLayout(
val li = LayoutInflater.from(context)
val entry = li.inflate(R.layout.header_stack_item, list, false) as HeaderItemLayout
list.addView(entry.apply {
list.addView(entry)
entry.run {
inputKey.setText(forHeader.key)
inputKey.post { entry.inputKey.requestFocus() }
attachHeader(forHeader, this@HeaderStackLayout)
@ -88,6 +77,6 @@ class HeaderStackLayout(
btnRemove.tag = headers.size - 1
btnRemove.setOnClickListener(this@HeaderStackLayout)
})
}
}
}

View file

@ -52,7 +52,7 @@ 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(