diff --git a/.gitignore b/.gitignore index 161128f..454e51a 100644 --- a/.gitignore +++ b/.gitignore @@ -180,4 +180,6 @@ gradle-app.setting .gradletasknamecache # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 -# gradle/wrapper/gradle-wrapper.properties \ No newline at end of file +# gradle/wrapper/gradle-wrapper.properties + +app/google-services.json \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 9d1e235..50f0406 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -40,7 +40,7 @@ - + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 771dddf..0000000 --- a/.travis.yml +++ /dev/null @@ -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 diff --git a/README.md b/README.md index 156123f..84ee16c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ ## 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) diff --git a/app/build.gradle b/app/build.gradle index efff452..d6b315e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,8 +4,6 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android-extensions' -apply from: '../fabric.gradle' - android { compileSdkVersion versions.compileSdk buildToolsVersion versions.buildTools @@ -18,14 +16,13 @@ android { versionName versions.publishVersion } - buildTypes { - debug { - ext.enableCrashlytics = false - buildConfigField "String", "FABRIC_API_KEY", "\"\"" - } - release { - buildConfigField "String", "FABRIC_API_KEY", "\"${getFabricApiKey()}\"" - } + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + + packagingOptions { + exclude 'META-INF/atomicfu.kotlin_module' } } @@ -41,6 +38,7 @@ 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 @@ -74,4 +72,8 @@ dependencies { androidTestImplementation 'androidx.test:rules:' + versions.androidxTestRunner } -apply from: '../spotless.gradle' \ No newline at end of file +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 diff --git a/app/src/main/java/com/afollestad/nocknock/NockNockApp.kt b/app/src/main/java/com/afollestad/nocknock/NockNockApp.kt index 2cd0af3..3c29301 100644 --- a/app/src/main/java/com/afollestad/nocknock/NockNockApp.kt +++ b/app/src/main/java/com/afollestad/nocknock/NockNockApp.kt @@ -47,10 +47,8 @@ class NockNockApp : Application() { Timber.plant(DebugTree()) } - if (BuildConfig.FABRIC_API_KEY.isNotEmpty()) { - Timber.plant(FabricTree()) - Fabric.with(this, Crashlytics()) - } + Timber.plant(FabricTree()) + Fabric.with(this, Crashlytics()) val modules = listOf( prefModule, 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 c43b729..3220567 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/DarkModeSwitchActivity.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/DarkModeSwitchActivity.kt @@ -15,10 +15,15 @@ */ 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 @@ -35,16 +40,35 @@ abstract class DarkModeSwitchActivity : AppCompatActivity() { setTheme(themeRes()) super.onCreate(savedInstanceState) - darkModePref.observe() - .filter { it != isDarkModeEnabled } - .subscribe { - log("Theme changed, recreating Activity.") - recreate() - } - .attachLifecycle(this) + if (getCurrentNightMode() == UNKNOWN) { + darkModePref.observe() + .filter { it != isDarkModeEnabled } + .subscribe { + log("Theme changed, recreating Activity.") + recreate() + } + .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()) diff --git a/app/src/main/java/com/afollestad/nocknock/ui/NightMode.kt b/app/src/main/java/com/afollestad/nocknock/ui/NightMode.kt new file mode 100644 index 0000000..2930fea --- /dev/null +++ b/app/src/main/java/com/afollestad/nocknock/ui/NightMode.kt @@ -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 +} 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 bd7796c..aec76d8 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 @@ -24,7 +24,6 @@ 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 @@ -33,10 +32,8 @@ 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 @@ -93,12 +90,17 @@ class MainActivity : DarkModeSwitchActivity() { toolbar.run { inflateMenu(R.menu.menu_main) menu.findItem(R.id.dark_mode) - .isChecked = isDarkMode() + .apply { + if (getCurrentNightMode() == UNKNOWN) { + isChecked = isDarkMode() + } else { + isVisible = false + } + } 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 } @@ -144,20 +146,4 @@ 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/viewsite/ViewSiteViewModel.kt b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModel.kt index f41a9a7..b5c9f93 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 @@ -223,7 +223,7 @@ class ViewSiteViewModel( private fun getUpdatedDbModel(): Site? { 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( validationIntervalMs = getCheckIntervalMs(), diff --git a/app/src/main/res/layout/activity_addsite.xml b/app/src/main/res/layout/activity_addsite.xml index f7bd3e8..80f83da 100644 --- a/app/src/main/res/layout/activity_addsite.xml +++ b/app/src/main/res/layout/activity_addsite.xml @@ -123,7 +123,7 @@ android:layout_height="wrap_content" android:layout_marginBottom="@dimen/content_inset" android:layout_marginTop="@dimen/content_inset_half" - android:background="@color/lighterGray" + android:background="?scriptLayoutBackground" /> - diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..80b730f --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + 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 new file mode 100644 index 0000000..80b730f --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index d1698c3..a86dbab 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 new file mode 100644 index 0000000..eb43a7b Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png 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 new file mode 100644 index 0000000..4567198 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index cccfa5b..60056be 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 new file mode 100644 index 0000000..666c904 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png 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 new file mode 100644 index 0000000..3baff41 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index a4f8356..27f30d2 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 new file mode 100644 index 0000000..4224797 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png 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 new file mode 100644 index 0000000..1d29a54 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 49dd226..60a8d1a 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 new file mode 100644 index 0000000..7cf19eb Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png 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 new file mode 100644 index 0000000..1548eb9 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index bd57c86..ac61bd3 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 new file mode 100644 index 0000000..9c43fc9 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png 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 new file mode 100644 index 0000000..04806fd Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index db28598..84ad226 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -13,10 +13,4 @@ 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 0df2f69..35d3041 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -4,5 +4,6 @@ + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d69b862..f840cee 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,7 +7,9 @@ #212121 #252525 - #303030 + #303030 + #EEEEEE + #FF6E40 #E44615 #40FF6E40 diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..70daa76 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #758F9A + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2267417..3ac40a0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ LinkedIn

Nock Nock is open source! Check out the GitHub page!
Icon by Kevin Aguilar of 221 Pixels. +
View the Privacy Policy. ]]> Dark Mode @@ -84,14 +85,6 @@ 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 1cb4a89..e4b435a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -6,7 +6,7 @@ diff --git a/app/src/main/res/values/styles_parents.xml b/app/src/main/res/values/styles_parents.xml index decbdb8..dc5e3d6 100644 --- a/app/src/main/res/values/styles_parents.xml +++ b/app/src/main/res/values/styles_parents.xml @@ -9,6 +9,7 @@ #000000 #EEEEEE #000000 + @color/lighterGray #212121 #727272 @@ -33,6 +34,7 @@ #ffffff #303030 #FFFFFF + @color/darkerGray #FFFFFF #F0F0F0 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 b62f9a6..1319310 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,20 +17,21 @@ 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 @@ -159,19 +160,24 @@ 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(), never()).insert(any()) + verify(database.validationResultsDao()).insert(validationResultCaptor.capture()) 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 = null + lastResult = result ) + assertThat(result.reason).isNull() + assertThat(result.status).isEqualTo(WAITING) + verify(validationManager).scheduleValidation( site = model, rightNow = true, @@ -191,5 +197,10 @@ 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/viewsite/ViewSiteViewModelTest.kt b/app/src/test/java/com/afollestad/nocknock/ui/viewsite/ViewSiteViewModelTest.kt index d7bc8a8..47fe6e0 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,6 +18,8 @@ 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 @@ -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.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 @@ -38,8 +41,10 @@ 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 @@ -255,6 +260,8 @@ class ViewSiteViewModelTest { } @Test fun commit_success() = runBlocking { + whenever(database.retryPolicyDao().forSite(any())).doReturn(listOf(fakeRetryPolicy(1))) + val isLoading = viewModel.onIsLoading() .test() @@ -267,11 +274,13 @@ 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( @@ -284,11 +293,13 @@ 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 + lastResult = updatedResult, + retryPolicy = retryPolicy ) assertThat(siteCaptor.firstValue).isEqualTo(updatedModel) @@ -373,5 +384,12 @@ 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/build.gradle b/build.gradle index d99c0ee..2d8e462 100644 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,7 @@ 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 } } @@ -22,7 +23,6 @@ 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 8472928..5004349 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -12,6 +12,10 @@ android { versionName versions.publishVersion } + packagingOptions { + exclude 'META-INF/atomicfu.kotlin_module' + } + // For Mozilla Rhino lintOptions { abortOnError false @@ -30,6 +34,7 @@ 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/data/build.gradle b/data/build.gradle index 5512756..e413d4c 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -14,6 +14,10 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + + packagingOptions { + exclude 'META-INF/atomicfu.kotlin_module' + } } dependencies { 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 db9edd7..e7d4400 100644 --- a/data/src/androidTest/java/com/afollestad/nocknock/data/AppDatabaseTest.kt +++ b/data/src/androidTest/java/com/afollestad/nocknock/data/AppDatabaseTest.kt @@ -431,9 +431,30 @@ class AppDatabaseTest() { val allSites = db.allSites() assertThat(allSites.size).isEqualTo(3) - assertThat(allSites[0]).isEqualTo(MOCK_MODEL_1) - assertThat(allSites[1]).isEqualTo(MOCK_MODEL_2) - assertThat(allSites[2]).isEqualTo(MOCK_MODEL_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) + ) + ) + ) } @Test fun extension_put_getSite() { @@ -470,10 +491,12 @@ 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" ) 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 6fc9796..2866b50 100644 --- a/data/src/androidTest/java/com/afollestad/nocknock/data/TestUtil.kt +++ b/data/src/androidTest/java/com/afollestad/nocknock/data/TestUtil.kt @@ -35,7 +35,8 @@ fun fakeSettingsModel( validationMode = validationMode, validationArgs = null, disabled = false, - networkTimeout = 10000 + networkTimeout = 10000, + certificate = null ) fun fakeResultModel( diff --git a/dependencies.gradle b/dependencies.gradle index f549cb5..e0e2412 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -3,53 +3,56 @@ ext.versions = [ minSdk : 21, compileSdk : 28, buildTools : '28.0.3', - publishVersion : '0.8.6b', - publishVersionCode : 41, + publishVersion : '0.8.8', + publishVersionCode : 46, // Plugins - gradlePlugin : '3.3.0', - spotlessPlugin : '3.17.0', - versionPlugin : '0.20.0', + gradlePlugin : '3.4.0', + spotlessPlugin : '3.22.0', + versionPlugin : '0.21.0', + googleServices : '4.2.0', fabricPlugin : '1.+', // Misc - okHttp : '3.12.1', + okHttp : '3.14.1', rhino : '1.7.10', // Kotlin - kotlin : '1.3.20', - coroutines : '1.1.1', + kotlin : '1.3.30', + coroutines : '1.2.0', koin : '1.0.2', // Google/AndroidX - androidxAnnotations : '1.0.1', + androidxAnnotations : '1.0.2', 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.0.0-rc9', - rxkPrefs : '1.2.2', - vvalidator : '0.2.2', + materialDialogs : '2.8.1', + rxkPrefs : '1.2.5', + vvalidator : '0.4.1', // Debugging timber : '4.7.1', - fabric : '2.9.8@aar', + fabric : '2.9.9@aar', // Unit testing junit : '4.12', - mockito : '2.23.4', + mockito : '2.27.0', mockitoKotlin : '2.1.0', - truth : '0.42', + truth : '0.44', // UI testing androidxTestRunner : '1.1.1', androidxTest : '1.1.0', - archTesting : '2.0.0' + archTesting : '2.0.1' ] diff --git a/fabric.gradle b/fabric.gradle deleted file mode 100644 index 315dff2..0000000 --- a/fabric.gradle +++ /dev/null @@ -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() -} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 9516cf5..c62d03d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 # 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 8ab96fb..2128899 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-4.10.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/ic_web.png b/ic_web.png index 509177f..d54b6ec 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 new file mode 100644 index 0000000..d9866d8 --- /dev/null +++ b/mock/mock-google-services.json @@ -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" +} diff --git a/mock/mock.gradle b/mock/mock.gradle new file mode 100644 index 0000000..2fa58ef --- /dev/null +++ b/mock/mock.gradle @@ -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() +} \ No newline at end of file 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 445c931..e543863 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 @@ -18,7 +18,6 @@ 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 @@ -32,18 +31,6 @@ 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) 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 index 092d416..6abff33 100644 --- a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/headers/HeaderStackLayout.kt +++ b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/headers/HeaderStackLayout.kt @@ -56,9 +56,22 @@ class HeaderStackLayout( override fun onClick(v: View) { val index = v.tag as Int - list.removeViewAt(index) - headers.removeAt(index) - postLiveData() + 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) { @@ -67,9 +80,7 @@ class HeaderStackLayout( val li = LayoutInflater.from(context) val entry = li.inflate(R.layout.header_stack_item, list, false) as HeaderItemLayout - list.addView(entry) - - entry.run { + list.addView(entry.apply { inputKey.setText(forHeader.key) inputKey.post { entry.inputKey.requestFocus() } attachHeader(forHeader, this@HeaderStackLayout) @@ -77,6 +88,6 @@ class HeaderStackLayout( btnRemove.tag = headers.size - 1 btnRemove.setOnClickListener(this@HeaderStackLayout) - } + }) } } 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 176f32d..1b1a29f 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 @@ -52,7 +52,7 @@ class JavaScriptInputLayout( contentInset // bottom ) elevation = dimenFloat(dimen.default_elevation) - inflate(context, layout.javascript_input_layout, this) + inflate(context, R.layout.javascript_input_layout, this) } fun attach(