diff --git a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivity.kt index 65e71c8..ab9f933 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivity.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivity.kt @@ -22,12 +22,14 @@ import com.afollestad.nocknock.viewcomponents.ext.conceal import com.afollestad.nocknock.viewcomponents.ext.onItemSelected import com.afollestad.nocknock.viewcomponents.ext.onLayout import com.afollestad.nocknock.viewcomponents.ext.showOrHide +import com.afollestad.nocknock.viewcomponents.ext.textAsInt import com.afollestad.nocknock.viewcomponents.ext.trimmedText import kotlinx.android.synthetic.main.activity_addsite.checkIntervalLayout import kotlinx.android.synthetic.main.activity_addsite.doneBtn import kotlinx.android.synthetic.main.activity_addsite.inputName import kotlinx.android.synthetic.main.activity_addsite.inputUrl import kotlinx.android.synthetic.main.activity_addsite.loadingProgress +import kotlinx.android.synthetic.main.activity_addsite.responseTimeoutInput import kotlinx.android.synthetic.main.activity_addsite.responseValidationMode import kotlinx.android.synthetic.main.activity_addsite.responseValidationSearchTerm import kotlinx.android.synthetic.main.activity_addsite.rootView @@ -99,6 +101,7 @@ class AddSiteActivity : AppCompatActivity(), AddSiteView { val checkInterval = checkIntervalLayout.getSelectedCheckInterval() val validationMode = responseValidationMode.selectedItemPosition.indexToValidationMode() + val defaultTimeout = getString(R.string.response_timeout_default).toInt() isClosing = true presenter.commit( @@ -106,7 +109,8 @@ class AddSiteActivity : AppCompatActivity(), AddSiteView { url = inputUrl.trimmedText(), checkInterval = checkInterval, validationMode = validationMode, - validationContent = validationMode.validationContent() + validationContent = validationMode.validationContent(), + networkTimeout = responseTimeoutInput.textAsInt(defaultValue = defaultTimeout) ) } } @@ -165,6 +169,11 @@ class AddSiteActivity : AppCompatActivity(), AddSiteView { null } ) + responseTimeoutInput.error = if (errors.networkTimeout != null) { + getString(errors.networkTimeout!!) + } else { + null + } } override fun onSiteAdded() { diff --git a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSitePresenter.kt b/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSitePresenter.kt index 61e1b7e..e95d379 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSitePresenter.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSitePresenter.kt @@ -27,11 +27,12 @@ data class InputErrors( var url: Int? = null, var checkInterval: Int? = null, var termSearch: Int? = null, - var javaScript: Int? = null + var javaScript: Int? = null, + var networkTimeout: Int? = null ) { @CheckResult fun any(): Boolean { return name != null || url != null || checkInterval != null || - termSearch != null || javaScript != null + termSearch != null || javaScript != null || networkTimeout != null } } @@ -52,7 +53,8 @@ interface AddSitePresenter { url: String, checkInterval: Long, validationMode: ValidationMode, - validationContent: String? + validationContent: String?, + networkTimeout: Int ) fun dropView() @@ -106,7 +108,8 @@ class RealAddSitePresenter @Inject constructor( url: String, checkInterval: Long, validationMode: ValidationMode, - validationContent: String? + validationContent: String?, + networkTimeout: Int ) { val inputErrors = InputErrors() @@ -126,6 +129,9 @@ class RealAddSitePresenter @Inject constructor( } else if (validationMode == JAVASCRIPT && validationContent.isNullOrEmpty()) { inputErrors.javaScript = R.string.please_enter_javaScript } + if (networkTimeout <= 0) { + inputErrors.networkTimeout = R.string.please_enter_networkTimeout + } if (inputErrors.any()) { view?.setInputErrors(inputErrors) @@ -138,7 +144,8 @@ class RealAddSitePresenter @Inject constructor( status = WAITING, checkInterval = checkInterval, validationMode = validationMode, - validationContent = validationContent + validationContent = validationContent, + networkTimeout = networkTimeout ) with(view!!) { diff --git a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt index 59d2a69..c44510f 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt @@ -33,6 +33,7 @@ import com.afollestad.nocknock.viewcomponents.ext.dimenFloat import com.afollestad.nocknock.viewcomponents.ext.onItemSelected import com.afollestad.nocknock.viewcomponents.ext.onScroll import com.afollestad.nocknock.viewcomponents.ext.showOrHide +import com.afollestad.nocknock.viewcomponents.ext.textAsInt import com.afollestad.nocknock.viewcomponents.ext.trimmedText import kotlinx.android.synthetic.main.activity_viewsite.checkIntervalLayout import kotlinx.android.synthetic.main.activity_viewsite.disableChecksButton @@ -41,6 +42,7 @@ import kotlinx.android.synthetic.main.activity_viewsite.iconStatus import kotlinx.android.synthetic.main.activity_viewsite.inputName import kotlinx.android.synthetic.main.activity_viewsite.inputUrl import kotlinx.android.synthetic.main.activity_viewsite.loadingProgress +import kotlinx.android.synthetic.main.activity_viewsite.responseTimeoutInput import kotlinx.android.synthetic.main.activity_viewsite.responseValidationMode import kotlinx.android.synthetic.main.activity_viewsite.responseValidationSearchTerm import kotlinx.android.synthetic.main.activity_viewsite.rootView @@ -113,13 +115,15 @@ class ViewSiteActivity : AppCompatActivity(), ViewSiteView { val checkInterval = checkIntervalLayout.getSelectedCheckInterval() val validationMode = responseValidationMode.selectedItemPosition.indexToValidationMode() + val defaultTimeout = getString(R.string.response_timeout_default).toInt() presenter.commit( name = inputName.trimmedText(), url = inputUrl.trimmedText(), checkInterval = checkInterval, validationMode = validationMode, - validationContent = validationMode.validationContent() + validationContent = validationMode.validationContent(), + networkTimeout = responseTimeoutInput.textAsInt(defaultValue = defaultTimeout) ) } @@ -189,6 +193,8 @@ class ViewSiteActivity : AppCompatActivity(), ViewSiteView { } } + responseTimeoutInput.setText(model.networkTimeout.toString()) + disableChecksButton.showOrHide(!this.disabled) doneBtn.setText( if (this.disabled) R.string.renable_and_save_changes @@ -228,6 +234,11 @@ class ViewSiteActivity : AppCompatActivity(), ViewSiteView { null } ) + responseTimeoutInput.error = if (errors.networkTimeout != null) { + getString(errors.networkTimeout!!) + } else { + null + } } override fun scopeWhileAttached( diff --git a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSitePresenter.kt b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSitePresenter.kt index 376b3c0..0241979 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSitePresenter.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSitePresenter.kt @@ -34,11 +34,12 @@ data class InputErrors( var url: Int? = null, var checkInterval: Int? = null, var termSearch: Int? = null, - var javaScript: Int? = null + var javaScript: Int? = null, + var networkTimeout: Int? = null ) { @CheckResult fun any(): Boolean { return name != null || url != null || checkInterval != null || - termSearch != null || javaScript != null + termSearch != null || javaScript != null || networkTimeout != null } } @@ -66,7 +67,8 @@ interface ViewSitePresenter { url: String, checkInterval: Long, validationMode: ValidationMode, - validationContent: String? + validationContent: String?, + networkTimeout: Int ) fun checkNow() @@ -151,7 +153,8 @@ class RealViewSitePresenter @Inject constructor( url: String, checkInterval: Long, validationMode: ValidationMode, - validationContent: String? + validationContent: String?, + networkTimeout: Int ) { val inputErrors = InputErrors() @@ -171,6 +174,9 @@ class RealViewSitePresenter @Inject constructor( } else if (validationMode == JAVASCRIPT && validationContent.isNullOrEmpty()) { inputErrors.javaScript = R.string.please_enter_javaScript } + if (networkTimeout <= 0) { + inputErrors.networkTimeout = R.string.please_enter_networkTimeout + } if (inputErrors.any()) { view?.setInputErrors(inputErrors) @@ -184,7 +190,8 @@ class RealViewSitePresenter @Inject constructor( checkInterval = checkInterval, validationMode = validationMode, validationContent = validationContent, - disabled = false + disabled = false, + networkTimeout = networkTimeout ) with(view!!) { diff --git a/app/src/main/res/layout/activity_addsite.xml b/app/src/main/res/layout/activity_addsite.xml index 90298cb..c19fba1 100644 --- a/app/src/main/res/layout/activity_addsite.xml +++ b/app/src/main/res/layout/activity_addsite.xml @@ -100,6 +100,28 @@ android:layout_marginTop="@dimen/content_inset" /> + + + + + + + + Please input a check interval. Please input a search term. Please input a validation script. + Please enter a network timeout greater than 0. Options Already checking sites! @@ -49,6 +50,9 @@ Disable Enable Checks & Save Changes + Network Response Timeout + 10000 + Refresh Status diff --git a/app/src/test/java/com/afollestad/nocknock/AddSitePresenterTest.kt b/app/src/test/java/com/afollestad/nocknock/AddSitePresenterTest.kt index 692c5ca..d1941eb 100644 --- a/app/src/test/java/com/afollestad/nocknock/AddSitePresenterTest.kt +++ b/app/src/test/java/com/afollestad/nocknock/AddSitePresenterTest.kt @@ -113,7 +113,8 @@ class AddSitePresenterTest { "https://test.com", 1, STATUS_CODE, - null + null, + 60000 ) val inputErrorsCaptor = argumentCaptor() @@ -131,7 +132,8 @@ class AddSitePresenterTest { "", 1, STATUS_CODE, - null + null, + 60000 ) val inputErrorsCaptor = argumentCaptor() @@ -149,7 +151,8 @@ class AddSitePresenterTest { "ftp://hello.com", 1, STATUS_CODE, - null + null, + 60000 ) val inputErrorsCaptor = argumentCaptor() @@ -167,7 +170,8 @@ class AddSitePresenterTest { "https://hello.com", -1, STATUS_CODE, - null + null, + 60000 ) val inputErrorsCaptor = argumentCaptor() @@ -185,7 +189,8 @@ class AddSitePresenterTest { "https://hello.com", 1, TERM_SEARCH, - null + null, + 60000 ) val inputErrorsCaptor = argumentCaptor() @@ -197,13 +202,33 @@ class AddSitePresenterTest { assertThat(errors.termSearch).isEqualTo(R.string.please_enter_search_term) } + @Test fun commit_networkTimeout_error() { + presenter.commit( + "Testing", + "https://hello.com", + 1, + STATUS_CODE, + null, + 0 + ) + + val inputErrorsCaptor = argumentCaptor() + verify(view).setInputErrors(inputErrorsCaptor.capture()) + verify(checkStatusManager, never()) + .scheduleCheck(any(), any(), any(), any()) + + val errors = inputErrorsCaptor.firstValue + assertThat(errors.networkTimeout).isEqualTo(R.string.please_enter_networkTimeout) + } + @Test fun commit_javaScript_error() { presenter.commit( "Testing", "https://hello.com", 1, JAVASCRIPT, - null + null, + 60000 ) val inputErrorsCaptor = argumentCaptor() @@ -221,7 +246,8 @@ class AddSitePresenterTest { "https://hello.com", 1, STATUS_CODE, - null + null, + 60000 ) val modelCaptor = argumentCaptor() diff --git a/app/src/test/java/com/afollestad/nocknock/ViewSitePresenterTest.kt b/app/src/test/java/com/afollestad/nocknock/ViewSitePresenterTest.kt index d938966..eb7b7f8 100644 --- a/app/src/test/java/com/afollestad/nocknock/ViewSitePresenterTest.kt +++ b/app/src/test/java/com/afollestad/nocknock/ViewSitePresenterTest.kt @@ -156,7 +156,8 @@ class ViewSitePresenterTest { "https://test.com", 1, STATUS_CODE, - null + null, + 60000 ) val inputErrorsCaptor = argumentCaptor() @@ -174,7 +175,8 @@ class ViewSitePresenterTest { "", 1, STATUS_CODE, - null + null, + 60000 ) val inputErrorsCaptor = argumentCaptor() @@ -192,7 +194,8 @@ class ViewSitePresenterTest { "ftp://hello.com", 1, STATUS_CODE, - null + null, + 60000 ) val inputErrorsCaptor = argumentCaptor() @@ -210,7 +213,8 @@ class ViewSitePresenterTest { "https://hello.com", -1, STATUS_CODE, - null + null, + 60000 ) val inputErrorsCaptor = argumentCaptor() @@ -228,7 +232,8 @@ class ViewSitePresenterTest { "https://hello.com", 1, TERM_SEARCH, - null + null, + 60000 ) val inputErrorsCaptor = argumentCaptor() @@ -246,7 +251,8 @@ class ViewSitePresenterTest { "https://hello.com", 1, JAVASCRIPT, - null + null, + 60000 ) val inputErrorsCaptor = argumentCaptor() @@ -258,6 +264,25 @@ class ViewSitePresenterTest { assertThat(errors.javaScript).isEqualTo(R.string.please_enter_javaScript) } + @Test fun commit_networkTimeout_error() { + presenter.commit( + "Testing", + "https://hello.com", + 1, + STATUS_CODE, + null, + 0 + ) + + val inputErrorsCaptor = argumentCaptor() + verify(view).setInputErrors(inputErrorsCaptor.capture()) + verify(checkStatusManager, never()) + .scheduleCheck(any(), any(), any(), any()) + + val errors = inputErrorsCaptor.firstValue + assertThat(errors.networkTimeout).isEqualTo(R.string.please_enter_networkTimeout) + } + @Test fun commit_success() = runBlocking { val name = "Testing" val url = "https://hello.com" @@ -274,7 +299,8 @@ class ViewSitePresenterTest { url, checkInterval, validationMode, - validationContent + validationContent, + 60000 ) val modelCaptor = argumentCaptor() @@ -358,4 +384,4 @@ class ViewSitePresenterTest { on { getAction() } doReturn action } } -} \ No newline at end of file +} diff --git a/data/src/main/java/com/afollestad/nocknock/data/ServerModel.kt b/data/src/main/java/com/afollestad/nocknock/data/ServerModel.kt index ecba970..b98931f 100644 --- a/data/src/main/java/com/afollestad/nocknock/data/ServerModel.kt +++ b/data/src/main/java/com/afollestad/nocknock/data/ServerModel.kt @@ -27,7 +27,8 @@ data class ServerModel( val reason: String? = null, val validationMode: ValidationMode, val validationContent: String? = null, - val disabled: Boolean = false + val disabled: Boolean = false, + val networkTimeout: Int = 0 ) : IdProvider { companion object { @@ -42,6 +43,7 @@ data class ServerModel( const val COLUMN_VALIDATION_MODE = "validation_mode" const val COLUMN_VALIDATION_CONTENT = "validation_content" const val COLUMN_DISABLED = "disabled" + const val COLUMN_NETWORK_TIMEOUT = "network_timeout" const val DEFAULT_SORT_ORDER = "$COLUMN_NAME ASC, $COLUMN_DISABLED DESC" @@ -58,7 +60,8 @@ data class ServerModel( cursor.getColumnIndex(COLUMN_VALIDATION_MODE) ).toValidationMode(), validationContent = cursor.getString(cursor.getColumnIndex(COLUMN_VALIDATION_CONTENT)), - disabled = cursor.getInt(cursor.getColumnIndex(COLUMN_DISABLED)) == 1 + disabled = cursor.getInt(cursor.getColumnIndex(COLUMN_DISABLED)) == 1, + networkTimeout = cursor.getInt(cursor.getColumnIndex(COLUMN_NETWORK_TIMEOUT)) ) } } @@ -81,5 +84,6 @@ data class ServerModel( put(COLUMN_VALIDATION_MODE, validationMode.value) put(COLUMN_VALIDATION_CONTENT, validationContent) put(COLUMN_DISABLED, disabled) + put(COLUMN_NETWORK_TIMEOUT, networkTimeout) } } diff --git a/engine/src/main/java/com/afollestad/nocknock/engine/db/ServerModelDbHelper.kt b/engine/src/main/java/com/afollestad/nocknock/engine/db/ServerModelDbHelper.kt index 4caaca1..d1bff17 100644 --- a/engine/src/main/java/com/afollestad/nocknock/engine/db/ServerModelDbHelper.kt +++ b/engine/src/main/java/com/afollestad/nocknock/engine/db/ServerModelDbHelper.kt @@ -21,7 +21,9 @@ private const val SQL_CREATE_ENTRIES = "${ServerModel.COLUMN_REASON} TEXT," + "${ServerModel.COLUMN_VALIDATION_MODE} INTEGER," + "${ServerModel.COLUMN_VALIDATION_CONTENT} TEXT," + - "${ServerModel.COLUMN_DISABLED} INTEGER)" + "${ServerModel.COLUMN_DISABLED} INTEGER," + + "${ServerModel.COLUMN_NETWORK_TIMEOUT} INTEGER" + + ")" private const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS ${ServerModel.TABLE_NAME}" @@ -30,7 +32,7 @@ class ServerModelDbHelper(context: Context) : SQLiteOpenHelper( context, DATABASE_NAME, null, DATABASE_VERSION ) { companion object { - const val DATABASE_VERSION = 2 + const val DATABASE_VERSION = 3 const val DATABASE_NAME = "ServerModels.db" } @@ -43,8 +45,12 @@ class ServerModelDbHelper(context: Context) : SQLiteOpenHelper( oldVersion: Int, newVersion: Int ) { - db.execSQL(SQL_DELETE_ENTRIES) - onCreate(db) + if (newVersion == 3 && oldVersion == 2) { + db.execSQL( + "ALTER TABLE ${ServerModel.TABLE_NAME} " + + "ADD COLUMN ${ServerModel.COLUMN_NETWORK_TIMEOUT} INTEGER DEFAULT 10000" + ) + } } override fun onDowngrade( diff --git a/engine/src/main/java/com/afollestad/nocknock/engine/statuscheck/CheckStatusManager.kt b/engine/src/main/java/com/afollestad/nocknock/engine/statuscheck/CheckStatusManager.kt index 39e22fd..7b0c68e 100644 --- a/engine/src/main/java/com/afollestad/nocknock/engine/statuscheck/CheckStatusManager.kt +++ b/engine/src/main/java/com/afollestad/nocknock/engine/statuscheck/CheckStatusManager.kt @@ -19,7 +19,9 @@ import com.afollestad.nocknock.utilities.providers.StringProvider import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response +import org.jetbrains.annotations.TestOnly import java.net.SocketTimeoutException +import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject import timber.log.Timber.d as log @@ -29,6 +31,8 @@ data class CheckResult( val response: Response? = null ) +typealias ClientTimeoutChanger = (client: OkHttpClient, timeout: Int) -> OkHttpClient + /** @author Aidan Follestad (@afollestad) */ interface CheckStatusManager { @@ -55,6 +59,12 @@ class RealCheckStatusManager @Inject constructor( private val siteStore: ServerModelStore ) : CheckStatusManager { + private var clientTimeoutChanger: ClientTimeoutChanger = { client, timeout -> + client.newBuilder() + .callTimeout(timeout.toLong(), MILLISECONDS) + .build() + } + override suspend fun ensureScheduledChecks() { val sites = siteStore.get() if (sites.isEmpty()) { @@ -121,6 +131,7 @@ class RealCheckStatusManager @Inject constructor( override suspend fun performCheck(site: ServerModel): CheckResult { check(site.id != 0) { "Cannot schedule checks for jobs with no ID." } + check(site.networkTimeout > 0) { "Network timeout not set for site ${site.id}" } log("performCheck(${site.id}) - GET ${site.url}") val request = Request.Builder() @@ -129,8 +140,10 @@ class RealCheckStatusManager @Inject constructor( .build() return try { - val response = okHttpClient.newCall(request) + val client = clientTimeoutChanger(okHttpClient, site.networkTimeout) + val response = client.newCall(request) .execute() + if (response.isSuccessful || response.code() == 401) { log("performCheck(${site.id}) = Successful") CheckResult( @@ -164,4 +177,8 @@ class RealCheckStatusManager @Inject constructor( private fun jobForSite(site: ServerModel) = jobScheduler.allPendingJobs .firstOrNull { job -> job.id == site.id } + + @TestOnly fun setClientTimeoutChanger(changer: ClientTimeoutChanger) { + this.clientTimeoutChanger = changer + } } diff --git a/engine/src/test/java/com/afollestad/nocknock/engine/CheckStatusManagerTest.kt b/engine/src/test/java/com/afollestad/nocknock/engine/CheckStatusManagerTest.kt index d931a50..71e45bb 100644 --- a/engine/src/test/java/com/afollestad/nocknock/engine/CheckStatusManagerTest.kt +++ b/engine/src/test/java/com/afollestad/nocknock/engine/CheckStatusManagerTest.kt @@ -55,7 +55,12 @@ class CheckStatusManagerTest { bundleProvider, jobInfoProvider, store - ) + ).apply { + setClientTimeoutChanger { _, timeout -> + whenever(okHttpClient.callTimeoutMillis()).doReturn(timeout) + return@setClientTimeoutChanger okHttpClient + } + } @Test fun ensureScheduledChecks_noEnabledSites() = runBlocking { val model1 = fakeModel().copy(disabled = true) @@ -234,6 +239,8 @@ class CheckStatusManagerTest { reason = null ) ) + assertThat(okHttpClient.callTimeoutMillis()) + .isEqualTo(model1.networkTimeout) } @Test fun performCheck_401_butStillSuccess() = runBlocking { @@ -280,7 +287,8 @@ class CheckStatusManagerTest { id = 1, name = "Wakanda Forever", url = "https://www.wakanda.gov", - validationMode = STATUS_CODE + validationMode = STATUS_CODE, + networkTimeout = 60000 ) private fun fakeJob(id: Int): JobInfo { diff --git a/utilities/src/main/java/com/afollestad/nocknock/utilities/ext/TimeExt.kt b/utilities/src/main/java/com/afollestad/nocknock/utilities/ext/TimeExt.kt index b3fdc20..e659d16 100644 --- a/utilities/src/main/java/com/afollestad/nocknock/utilities/ext/TimeExt.kt +++ b/utilities/src/main/java/com/afollestad/nocknock/utilities/ext/TimeExt.kt @@ -24,7 +24,13 @@ fun Long.timeString() = when { "${ceil((this.toFloat() / DAY.toFloat()).toDouble()).toInt()}d" this >= HOUR -> "${ceil((this.toFloat() / HOUR.toFloat()).toDouble()).toInt()}h" - this >= MINUTE -> - "${ceil((this.toFloat() / MINUTE.toFloat()).toDouble()).toInt()}m" + this >= MINUTE -> { + val result = "${ceil((this.toFloat() / MINUTE.toFloat()).toDouble()).toInt()}m" + if (result == "60m") { + "1h" + } else { + result + } + } else -> "<1m" } diff --git a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/ext/TextViewExt.kt b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/ext/TextViewExt.kt index fc34583..533a8f1 100644 --- a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/ext/TextViewExt.kt +++ b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/ext/TextViewExt.kt @@ -9,9 +9,14 @@ import android.widget.TextView fun TextView.trimmedText() = text.toString().trim() -fun TextView.textAsLong(): Long { +fun TextView.textAsInt(defaultValue: Int = 0): Int { val text = trimmedText() - return if (text.isEmpty()) 0L else text.toLong() + return if (text.isEmpty()) defaultValue else text.toInt() +} + +fun TextView.textAsLong(defaultValue: Long = 0L): Long { + val text = trimmedText() + return if (text.isEmpty()) defaultValue else text.toLong() } ///** @author https://stackoverflow.com/a/53296137/309644 */