Compare commits

..

30 commits

Author SHA1 Message Date
Aidan Follestad
23ba4a69cd
Delete .travis.yml 2020-02-24 11:50:16 -08:00
Aidan Follestad
dd9aec1dff
Update README.md 2020-02-24 11:50:05 -08:00
Aidan Follestad
406af590aa Increment version code 2019-04-19 10:52:10 -07:00
Aidan Follestad
550f8c59be Adaptive-ish icon 2019-04-19 10:51:54 -07:00
Aidan Follestad
10d7fe33f9 Fix a few test cases 2019-04-18 19:09:08 -07:00
Aidan Follestad
35eda8f057 Exclude META-INF/atomicfu.kotlin_module from data module 2019-04-18 18:35:20 -07:00
Aidan Follestad
a0fd44ae7a Exclude META-INF/atomicfu.kotlin_module from common module 2019-04-18 18:06:27 -07:00
Aidan Follestad
351f718df8 0.8.8 2019-04-18 17:57:20 -07:00
Aidan Follestad
e2f7db22d1 Still trying to fix Travis 2019-04-18 17:48:27 -07:00
Aidan Follestad
82c1a17c68 Fix crash when mremoving headers, resolves #48 2019-04-18 17:36:49 -07:00
Aidan Follestad
a6670e2bea Fix Travis build 2019-04-18 17:20:09 -07:00
Aidan Follestad
5fc1569099 Remove donation options 2019-04-18 16:19:10 -07:00
Aidan Follestad
0770db5df5 Attempt to fix Travis.ci running UI tests 2019-04-18 16:12:06 -07:00
Aidan Follestad
97a0eda92c Dep and Gradle Plugin updates 2019-04-18 16:07:24 -07:00
Aidan Follestad
1ccb89bfc3 Dependency upgrades 2019-04-15 20:46:36 -07:00
Aidan Follestad
9ea9c78099 Add privacy policy link to the about dialog 2019-04-15 20:37:35 -07:00
Aidan Follestad
997c797598
Kotlin 1.3.30 2019-04-11 12:56:23 -07:00
Aidan Follestad
b26543d244
Update dependencies.gradle 2019-03-16 12:08:29 -07:00
Aidan Follestad
8c3654c4ac 0.8.7 2019-03-14 15:06:38 -07:00
Aidan Follestad
df2652860e -am 2019-03-14 15:06:03 -07:00
Aidan Follestad
4da8cb5f11 Fix tests 2019-03-14 15:01:24 -07:00
Aidan Follestad
334e9e823c Switch to Firebase for Crashlytics 2019-03-14 13:55:19 -07:00
Aidan Follestad
6d382b93a5 Dependency updates 2019-02-21 14:43:26 -08:00
Aidan Follestad
ef18464728
vvalidator 0.3.0 2019-02-01 17:57:23 -08:00
Aidan Follestad
872e99d80d Custom repo not needed anymore for Material Dialogs 2019-02-01 15:21:11 -08:00
Aidan Follestad
7f507792a8
Update gradle-wrapper.properties 2019-01-30 13:53:10 -08:00
Aidan Follestad
68b6944542 0.8.6c 2019-01-26 18:42:16 -08:00
Aidan Follestad
e39093b526 Fix script input layout background color 2019-01-26 18:41:55 -08:00
Aidan Follestad
9514a5ec83 0.8.6b 2019-01-26 14:55:26 -08:00
Aidan Follestad
3e5b1d4d8e Resolve two crashes 2019-01-26 14:55:06 -08:00
54 changed files with 286 additions and 173 deletions

4
.gitignore vendored
View file

@ -180,4 +180,6 @@ gradle-app.setting
.gradletasknamecache .gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties # gradle/wrapper/gradle-wrapper.properties
app/google-services.json

View file

@ -1,22 +0,0 @@
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,6 +1,5 @@
## Nock Nock ## 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) [![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) ![Showcase](https://raw.githubusercontent.com/afollestad/nock-nock/master/art/showcase5.png)

View file

@ -4,8 +4,6 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply from: '../fabric.gradle'
android { android {
compileSdkVersion versions.compileSdk compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools buildToolsVersion versions.buildTools
@ -18,14 +16,13 @@ android {
versionName versions.publishVersion versionName versions.publishVersion
} }
buildTypes { compileOptions {
debug { sourceCompatibility 1.8
ext.enableCrashlytics = false targetCompatibility 1.8
buildConfigField "String", "FABRIC_API_KEY", "\"\"" }
}
release { packagingOptions {
buildConfigField "String", "FABRIC_API_KEY", "\"${getFabricApiKey()}\"" exclude 'META-INF/atomicfu.kotlin_module'
}
} }
} }
@ -41,6 +38,7 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:' + versions.androidxRecyclerView implementation 'androidx.recyclerview:recyclerview:' + versions.androidxRecyclerView
implementation 'com.google.android.material:material:' + versions.googleMaterial implementation 'com.google.android.material:material:' + versions.googleMaterial
implementation 'androidx.browser:browser:' + versions.androidxBrowser implementation 'androidx.browser:browser:' + versions.androidxBrowser
implementation 'com.google.firebase:firebase-core:' + versions.firebaseCore
// Lifecycle // Lifecycle
kapt 'androidx.lifecycle:lifecycle-compiler:' + versions.lifecycle kapt 'androidx.lifecycle:lifecycle-compiler:' + versions.lifecycle
@ -74,4 +72,8 @@ dependencies {
androidTestImplementation 'androidx.test:rules:' + versions.androidxTestRunner androidTestImplementation 'androidx.test:rules:' + versions.androidxTestRunner
} }
apply from: '../spotless.gradle' apply from: '../spotless.gradle'
apply from: '../mock/mock.gradle'
apply plugin: "io.fabric"
apply plugin: 'com.google.gms.google-services'

View file

@ -47,10 +47,8 @@ class NockNockApp : Application() {
Timber.plant(DebugTree()) Timber.plant(DebugTree())
} }
if (BuildConfig.FABRIC_API_KEY.isNotEmpty()) { Timber.plant(FabricTree())
Timber.plant(FabricTree()) Fabric.with(this, Crashlytics())
Fabric.with(this, Crashlytics())
}
val modules = listOf( val modules = listOf(
prefModule, prefModule,

View file

@ -15,10 +15,15 @@
*/ */
package com.afollestad.nocknock.ui package com.afollestad.nocknock.ui
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.afollestad.nocknock.R import com.afollestad.nocknock.R
import com.afollestad.nocknock.koin.PREF_DARK_MODE 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.nocknock.utilities.rx.attachLifecycle
import com.afollestad.rxkprefs.Pref import com.afollestad.rxkprefs.Pref
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
@ -35,16 +40,35 @@ abstract class DarkModeSwitchActivity : AppCompatActivity() {
setTheme(themeRes()) setTheme(themeRes())
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
darkModePref.observe() if (getCurrentNightMode() == UNKNOWN) {
.filter { it != isDarkModeEnabled } darkModePref.observe()
.subscribe { .filter { it != isDarkModeEnabled }
log("Theme changed, recreating Activity.") .subscribe {
recreate() log("Theme changed, recreating Activity.")
} recreate()
.attachLifecycle(this) }
.attachLifecycle(this)
}
} }
protected fun isDarkMode() = darkModePref.get() 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 toggleDarkMode() = setDarkMode(!isDarkMode()) protected fun toggleDarkMode() = setDarkMode(!isDarkMode())

View file

@ -0,0 +1,26 @@
/**
* 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,7 +24,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL import androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItems import com.afollestad.materialdialogs.list.listItems
import com.afollestad.materialdialogs.list.listItemsSingleChoice
import com.afollestad.nocknock.R import com.afollestad.nocknock.R
import com.afollestad.nocknock.adapter.SiteAdapter import com.afollestad.nocknock.adapter.SiteAdapter
import com.afollestad.nocknock.adapter.TagAdapter import com.afollestad.nocknock.adapter.TagAdapter
@ -33,10 +32,8 @@ import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.dialogs.AboutDialog import com.afollestad.nocknock.dialogs.AboutDialog
import com.afollestad.nocknock.notifications.NockNotificationManager import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.ui.DarkModeSwitchActivity 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.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 com.afollestad.nocknock.viewcomponents.livedata.toViewVisibility
import kotlinx.android.synthetic.main.activity_main.fab import kotlinx.android.synthetic.main.activity_main.fab
import kotlinx.android.synthetic.main.activity_main.list import kotlinx.android.synthetic.main.activity_main.list
@ -93,12 +90,17 @@ class MainActivity : DarkModeSwitchActivity() {
toolbar.run { toolbar.run {
inflateMenu(R.menu.menu_main) inflateMenu(R.menu.menu_main)
menu.findItem(R.id.dark_mode) menu.findItem(R.id.dark_mode)
.isChecked = isDarkMode() .apply {
if (getCurrentNightMode() == UNKNOWN) {
isChecked = isDarkMode()
} else {
isVisible = false
}
}
setOnMenuItemClickListener { item -> setOnMenuItemClickListener { item ->
when (item.itemId) { when (item.itemId) {
R.id.about -> AboutDialog.show(this@MainActivity) R.id.about -> AboutDialog.show(this@MainActivity)
R.id.dark_mode -> toggleDarkMode() R.id.dark_mode -> toggleDarkMode()
R.id.support_me -> supportMe()
} }
return@setOnMenuItemClickListener true return@setOnMenuItemClickListener true
} }
@ -144,20 +146,4 @@ class MainActivity : DarkModeSwitchActivity() {
viewSite(model) 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? { private fun getUpdatedDbModel(): Site? {
val timeout = timeout.value ?: 10_000 val timeout = timeout.value ?: 10_000
val cleanedTags = tags.value?.split(',')?.joinToString { it.trim() } ?: "" val cleanedTags = tags.value?.split(',')?.joinToString(separator = ",") ?: ""
val newSettings = site.settings!!.copy( val newSettings = site.settings!!.copy(
validationIntervalMs = getCheckIntervalMs(), validationIntervalMs = getCheckIntervalMs(),

View file

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

View file

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

View file

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

View file

@ -0,0 +1,5 @@
<?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

@ -0,0 +1,5 @@
<?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: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

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

View file

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

View file

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

View file

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

View file

@ -14,6 +14,7 @@
<a href=\'https://www.linkedin.com/in/afollestad\'>LinkedIn</a> <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/><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/>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>
<string name="dark_mode">Dark Mode</string> <string name="dark_mode">Dark Mode</string>
@ -84,14 +85,6 @@
exception to pass custom error messages to Nock Nock. exception to pass custom error messages to Nock Nock.
</string> </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> <string name="install_web_browser">Please install a web browser app, such as Google Chrome.</string>
</resources> </resources>

View file

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

View file

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

View file

@ -17,20 +17,21 @@ package com.afollestad.nocknock.ui.addsite
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.afollestad.nocknock.R 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.Site
import com.afollestad.nocknock.data.model.SiteSettings 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.JAVASCRIPT
import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE
import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH 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.ValidationExecutor
import com.afollestad.nocknock.mockDatabase import com.afollestad.nocknock.mockDatabase
import com.afollestad.nocknock.utilities.ext.MINUTE import com.afollestad.nocknock.utilities.ext.MINUTE
import com.afollestad.nocknock.utilities.livedata.test import com.afollestad.nocknock.utilities.livedata.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.argumentCaptor import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -159,19 +160,24 @@ class AddSiteViewModelTest {
val siteCaptor = argumentCaptor<Site>() val siteCaptor = argumentCaptor<Site>()
val settingsCaptor = argumentCaptor<SiteSettings>() val settingsCaptor = argumentCaptor<SiteSettings>()
val validationResultCaptor = argumentCaptor<ValidationResult>()
isLoading.assertValues(true, false) isLoading.assertValues(true, false)
verify(database.siteDao()).insert(siteCaptor.capture()) verify(database.siteDao()).insert(siteCaptor.capture())
verify(database.siteSettingsDao()).insert(settingsCaptor.capture()) verify(database.siteSettingsDao()).insert(settingsCaptor.capture())
verify(database.validationResultsDao(), never()).insert(any()) verify(database.validationResultsDao()).insert(validationResultCaptor.capture())
val settings = settingsCaptor.firstValue val settings = settingsCaptor.firstValue
val result = validationResultCaptor.firstValue.copy(siteId = 1)
val model = siteCaptor.firstValue.copy( val model = siteCaptor.firstValue.copy(
id = 1, // fill it in because our insert captor doesn't catch this id = 1, // fill it in because our insert captor doesn't catch this
settings = settings, settings = settings,
lastResult = null lastResult = result
) )
assertThat(result.reason).isNull()
assertThat(result.status).isEqualTo(WAITING)
verify(validationManager).scheduleValidation( verify(validationManager).scheduleValidation(
site = model, site = model,
rightNow = true, rightNow = true,
@ -191,5 +197,10 @@ class AddSiteViewModelTest {
validationScript.value = null validationScript.value = null
checkIntervalValue.value = 60 checkIntervalValue.value = 60
checkIntervalUnit.value = 1000 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,6 +18,8 @@ package com.afollestad.nocknock.ui.viewsite
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.afollestad.nocknock.MOCK_MODEL_1 import com.afollestad.nocknock.MOCK_MODEL_1
import com.afollestad.nocknock.R 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.Site
import com.afollestad.nocknock.data.model.SiteSettings import com.afollestad.nocknock.data.model.SiteSettings
import com.afollestad.nocknock.data.model.Status.CHECKING import com.afollestad.nocknock.data.model.Status.CHECKING
@ -29,6 +31,7 @@ import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE
import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH
import com.afollestad.nocknock.data.model.ValidationResult import com.afollestad.nocknock.data.model.ValidationResult
import com.afollestad.nocknock.engine.validation.ValidationExecutor import com.afollestad.nocknock.engine.validation.ValidationExecutor
import com.afollestad.nocknock.fakeRetryPolicy
import com.afollestad.nocknock.mockDatabase import com.afollestad.nocknock.mockDatabase
import com.afollestad.nocknock.notifications.NockNotificationManager import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.utilities.livedata.test import com.afollestad.nocknock.utilities.livedata.test
@ -38,8 +41,10 @@ import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.argumentCaptor import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.doAnswer import com.nhaarman.mockitokotlin2.doAnswer
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -255,6 +260,8 @@ class ViewSiteViewModelTest {
} }
@Test fun commit_success() = runBlocking { @Test fun commit_success() = runBlocking {
whenever(database.retryPolicyDao().forSite(any())).doReturn(listOf(fakeRetryPolicy(1)))
val isLoading = viewModel.onIsLoading() val isLoading = viewModel.onIsLoading()
.test() .test()
@ -267,11 +274,13 @@ class ViewSiteViewModelTest {
val siteCaptor = argumentCaptor<Site>() val siteCaptor = argumentCaptor<Site>()
val settingsCaptor = argumentCaptor<SiteSettings>() val settingsCaptor = argumentCaptor<SiteSettings>()
val resultCaptor = argumentCaptor<ValidationResult>() val resultCaptor = argumentCaptor<ValidationResult>()
val retryPolicyCaptor = argumentCaptor<RetryPolicy>()
isLoading.assertValues(true, false) isLoading.assertValues(true, false)
verify(database.siteDao()).update(siteCaptor.capture()) verify(database.siteDao()).update(siteCaptor.capture())
verify(database.siteSettingsDao()).update(settingsCaptor.capture()) verify(database.siteSettingsDao()).update(settingsCaptor.capture())
verify(database.validationResultsDao()).update(resultCaptor.capture()) verify(database.validationResultsDao()).update(resultCaptor.capture())
verify(database.retryPolicyDao()).update(retryPolicyCaptor.capture())
// From fillInModel() below // From fillInModel() below
val updatedSettings = MOCK_MODEL_1.settings!!.copy( val updatedSettings = MOCK_MODEL_1.settings!!.copy(
@ -284,11 +293,13 @@ class ViewSiteViewModelTest {
val updatedResult = MOCK_MODEL_1.lastResult!!.copy( val updatedResult = MOCK_MODEL_1.lastResult!!.copy(
status = WAITING status = WAITING
) )
val retryPolicy = retryPolicyCaptor.firstValue
val updatedModel = MOCK_MODEL_1.copy( val updatedModel = MOCK_MODEL_1.copy(
name = "Hello There", name = "Hello There",
url = "https://www.hellothere.com", url = "https://www.hellothere.com",
settings = updatedSettings, settings = updatedSettings,
lastResult = updatedResult lastResult = updatedResult,
retryPolicy = retryPolicy
) )
assertThat(siteCaptor.firstValue).isEqualTo(updatedModel) assertThat(siteCaptor.firstValue).isEqualTo(updatedModel)
@ -373,5 +384,12 @@ class ViewSiteViewModelTest {
validationScript.value = "throw 'Oh no!'" validationScript.value = "throw 'Oh no!'"
checkIntervalValue.value = 24 checkIntervalValue.value = 24
checkIntervalUnit.value = 60000 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,6 +15,7 @@ buildscript {
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:' + versions.kotlin classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:' + versions.kotlin
classpath 'com.github.ben-manes:gradle-versions-plugin:' + versions.versionPlugin classpath 'com.github.ben-manes:gradle-versions-plugin:' + versions.versionPlugin
classpath 'io.fabric.tools:gradle:' + versions.fabricPlugin classpath 'io.fabric.tools:gradle:' + versions.fabricPlugin
classpath 'com.google.gms:google-services:' + versions.googleServices
} }
} }
@ -22,7 +23,6 @@ allprojects {
repositories { repositories {
google() google()
jcenter() jcenter()
maven { url "https://dl.bintray.com/drummer-aidan/maven" }
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
} }

View file

@ -12,6 +12,10 @@ android {
versionName versions.publishVersion versionName versions.publishVersion
} }
packagingOptions {
exclude 'META-INF/atomicfu.kotlin_module'
}
// For Mozilla Rhino // For Mozilla Rhino
lintOptions { lintOptions {
abortOnError false abortOnError false
@ -30,6 +34,7 @@ dependencies {
implementation 'org.mozilla:rhino:' + versions.rhino implementation 'org.mozilla:rhino:' + versions.rhino
api 'com.afollestad:rxkprefs:' + versions.rxkPrefs api 'com.afollestad:rxkprefs:' + versions.rxkPrefs
api "io.reactivex.rxjava2:rxjava:" + versions.rxJava
testImplementation 'junit:junit:' + versions.junit testImplementation 'junit:junit:' + versions.junit
testImplementation 'com.google.truth:truth:' + versions.truth testImplementation 'com.google.truth:truth:' + versions.truth

View file

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

View file

@ -431,9 +431,30 @@ class AppDatabaseTest() {
val allSites = db.allSites() val allSites = db.allSites()
assertThat(allSites.size).isEqualTo(3) assertThat(allSites.size).isEqualTo(3)
assertThat(allSites[0]).isEqualTo(MOCK_MODEL_1) assertThat(allSites[0]).isEqualTo(
assertThat(allSites[1]).isEqualTo(MOCK_MODEL_2) MOCK_MODEL_1.copy(
assertThat(allSites[2]).isEqualTo(MOCK_MODEL_3) 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)
)
)
)
} }
@Test fun extension_put_getSite() { @Test fun extension_put_getSite() {
@ -470,10 +491,12 @@ class AppDatabaseTest() {
) )
val updatedHeaders = listOf( val updatedHeaders = listOf(
modelToUpdate.headers.first().copy( modelToUpdate.headers.first().copy(
id = 7,
key = "One", key = "One",
value = "Hello" value = "Hello"
), ),
modelToUpdate.headers.last().copy( modelToUpdate.headers.last().copy(
id = 8,
key = "Two", key = "Two",
value = "Hey" value = "Hey"
) )

View file

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

View file

@ -3,53 +3,56 @@ ext.versions = [
minSdk : 21, minSdk : 21,
compileSdk : 28, compileSdk : 28,
buildTools : '28.0.3', buildTools : '28.0.3',
publishVersion : '0.8.6', publishVersion : '0.8.8',
publishVersionCode : 40, publishVersionCode : 46,
// Plugins // Plugins
gradlePlugin : '3.3.0', gradlePlugin : '3.4.0',
spotlessPlugin : '3.17.0', spotlessPlugin : '3.22.0',
versionPlugin : '0.20.0', versionPlugin : '0.21.0',
googleServices : '4.2.0',
fabricPlugin : '1.+', fabricPlugin : '1.+',
// Misc // Misc
okHttp : '3.12.1', okHttp : '3.14.1',
rhino : '1.7.10', rhino : '1.7.10',
// Kotlin // Kotlin
kotlin : '1.3.20', kotlin : '1.3.30',
coroutines : '1.1.1', coroutines : '1.2.0',
koin : '1.0.2', koin : '1.0.2',
// Google/AndroidX // Google/AndroidX
androidxAnnotations : '1.0.1', androidxAnnotations : '1.0.2',
androidxCore : '1.0.2', androidxCore : '1.0.2',
androidxRecyclerView: '1.0.0', androidxRecyclerView: '1.0.0',
androidxBrowser : '1.0.0', androidxBrowser : '1.0.0',
googleMaterial : '1.0.0', googleMaterial : '1.0.0',
room : '2.0.0', room : '2.0.0',
lifecycle : '2.0.0', lifecycle : '2.0.0',
firebaseCore : '16.0.8',
// Rx // Rx
rxJava : '2.2.8',
rxBinding : '3.0.0-alpha1', rxBinding : '3.0.0-alpha1',
// afollestad // afollestad
materialDialogs : '2.0.0-rc9', materialDialogs : '2.8.1',
rxkPrefs : '1.2.2', rxkPrefs : '1.2.5',
vvalidator : '0.2.2', vvalidator : '0.4.1',
// Debugging // Debugging
timber : '4.7.1', timber : '4.7.1',
fabric : '2.9.8@aar', fabric : '2.9.9@aar',
// Unit testing // Unit testing
junit : '4.12', junit : '4.12',
mockito : '2.23.4', mockito : '2.27.0',
mockitoKotlin : '2.1.0', mockitoKotlin : '2.1.0',
truth : '0.42', truth : '0.44',
// UI testing // UI testing
androidxTestRunner : '1.1.1', androidxTestRunner : '1.1.1',
androidxTest : '1.1.0', androidxTest : '1.1.0',
archTesting : '2.0.0' archTesting : '2.0.1'
] ]

View file

@ -36,6 +36,7 @@ import okhttp3.Response
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import kotlin.math.max
import timber.log.Timber.d as log import timber.log.Timber.d as log
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
@ -154,21 +155,23 @@ class RealValidationExecutor(
check(site.id != 0L) { "Cannot schedule validations for jobs with no ID." } check(site.id != 0L) { "Cannot schedule validations for jobs with no ID." }
val siteSettings = site.settings val siteSettings = site.settings
requireNotNull(siteSettings) { "Site settings must be populated." } 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}") log("performValidation(${site.id}) - GET ${site.url}")
val request = Request.Builder() val request = Request.Builder()
.apply { .apply {
url(site.url) url(site.url)
get() get()
site.headers.forEach { header -> site.headers
addHeader(header.key, header.value) .filter { header -> header.key.isNotNullOrEmpty() }
} .forEach { header ->
addHeader(header.key, header.value)
}
} }
.build() .build()
return try { return try {
val clientWithTimeout = clientTimeoutChanger(okHttpClient, siteSettings.networkTimeout) val timeout = max(siteSettings.networkTimeout, 1)
val clientWithTimeout = clientTimeoutChanger(okHttpClient, timeout)
val client = if (siteSettings.certificate.isNotNullOrEmpty()) { val client = if (siteSettings.certificate.isNotNullOrEmpty()) {
sslManager.clientForCertificate( sslManager.clientForCertificate(
certUri = siteSettings.certificate!!, certUri = siteSettings.certificate!!,

View file

@ -1,29 +0,0 @@
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,3 +16,6 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro
# This option should only be used with decoupled projects. More details, visit # 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 # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
android.useAndroidX=true
android.enableJetifier=true

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before After
Before After

View file

@ -0,0 +1,42 @@
{
"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"
}

13
mock/mock.gradle Normal file
View file

@ -0,0 +1,13 @@
// 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,7 +18,6 @@ package com.afollestad.nocknock.viewcomponents.ext
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewTreeObserver
import androidx.annotation.DimenRes import androidx.annotation.DimenRes
import com.afollestad.vvalidator.form.Condition import com.afollestad.vvalidator.form.Condition
@ -32,18 +31,6 @@ fun View.hide() {
fun View.showOrHide(show: Boolean) = if (show) show() else hide() fun View.showOrHide(show: Boolean) = if (show) show() else hide()
fun View.onLayout(cb: () -> Unit) {
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.dimenFloat(@DimenRes res: Int) = resources.getDimension(res)
fun View.dimenInt(@DimenRes res: Int) = resources.getDimensionPixelSize(res) fun View.dimenInt(@DimenRes res: Int) = resources.getDimensionPixelSize(res)

View file

@ -56,9 +56,22 @@ class HeaderStackLayout(
override fun onClick(v: View) { override fun onClick(v: View) {
val index = v.tag as Int val index = v.tag as Int
list.removeViewAt(index) check(index >= 0 || index < list.childCount) {
headers.removeAt(index) "Index $index is out of bounds in the header stack (size ${list.childCount})."
postLiveData() }
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) { private fun addEntry(forHeader: Header) {
@ -67,9 +80,7 @@ class HeaderStackLayout(
val li = LayoutInflater.from(context) val li = LayoutInflater.from(context)
val entry = li.inflate(R.layout.header_stack_item, list, false) as HeaderItemLayout val entry = li.inflate(R.layout.header_stack_item, list, false) as HeaderItemLayout
list.addView(entry) list.addView(entry.apply {
entry.run {
inputKey.setText(forHeader.key) inputKey.setText(forHeader.key)
inputKey.post { entry.inputKey.requestFocus() } inputKey.post { entry.inputKey.requestFocus() }
attachHeader(forHeader, this@HeaderStackLayout) attachHeader(forHeader, this@HeaderStackLayout)
@ -77,6 +88,6 @@ class HeaderStackLayout(
btnRemove.tag = headers.size - 1 btnRemove.tag = headers.size - 1
btnRemove.setOnClickListener(this@HeaderStackLayout) btnRemove.setOnClickListener(this@HeaderStackLayout)
} })
} }
} }

View file

@ -52,7 +52,7 @@ class JavaScriptInputLayout(
contentInset // bottom contentInset // bottom
) )
elevation = dimenFloat(dimen.default_elevation) elevation = dimenFloat(dimen.default_elevation)
inflate(context, layout.javascript_input_layout, this) inflate(context, R.layout.javascript_input_layout, this)
} }
fun attach( fun attach(