diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 08b0af2..1f5ec28 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -52,6 +52,9 @@ + diff --git a/app/src/main/java/com/afollestad/nocknock/ui/AddSiteActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/AddSiteActivity.kt index 083ec7b..6d4d861 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/AddSiteActivity.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/AddSiteActivity.kt @@ -20,9 +20,11 @@ import androidx.appcompat.app.AppCompatActivity import com.afollestad.nocknock.R import com.afollestad.nocknock.data.ServerModel import com.afollestad.nocknock.data.ServerStatus.WAITING +import com.afollestad.nocknock.data.ValidationMode import com.afollestad.nocknock.data.ValidationMode.JAVASCRIPT import com.afollestad.nocknock.data.ValidationMode.STATUS_CODE import com.afollestad.nocknock.data.ValidationMode.TERM_SEARCH +import com.afollestad.nocknock.data.indexToValidationMode import com.afollestad.nocknock.engine.db.ServerModelStore import com.afollestad.nocknock.engine.statuscheck.CheckStatusManager import com.afollestad.nocknock.utilities.ext.injector @@ -56,6 +58,7 @@ import kotlinx.coroutines.launch import java.lang.System.currentTimeMillis import javax.inject.Inject import kotlin.math.max +import kotlin.properties.Delegates.notNull private const val KEY_FAB_X = "fab_x" private const val KEY_FAB_Y = "fab_y" @@ -76,11 +79,19 @@ fun MainActivity.intentToAdd( /** @author Aidan Follestad (afollestad) */ class AddSiteActivity : AppCompatActivity(), View.OnClickListener { + companion object { + private const val REVEAL_DURATION = 300L + } + private var isClosing: Boolean = false @Inject lateinit var serverModelStore: ServerModelStore @Inject lateinit var checkStatusManager: CheckStatusManager + private var revealCx by notNull() + private var revealCy by notNull() + private var revealRadius by notNull() + @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -91,7 +102,19 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener { if (savedInstanceState == null) { rootView.conceal() - rootView.onLayout { circularRevealActivity() } + rootView.onLayout { + val fabSize = intent.getIntExtra(KEY_FAB_SIZE, 0) + val fabX = intent.getFloatExtra(KEY_FAB_X, 0f) + .toInt() + val fabY = intent.getFloatExtra(KEY_FAB_Y, 0f) + .toInt() + + revealCx = fabX + fabSize / 2 + revealCy = (fabY + toolbar.measuredHeight + fabSize / 2) + revealRadius = max(revealCx, revealCy).toFloat() + + circularRevealActivity() + } } inputUrl.setOnFocusChangeListener { _, hasFocus -> @@ -141,27 +164,23 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener { doneBtn.setOnClickListener(this) } + private fun circularRevealActivity() { + val circularReveal = + createCircularReveal(rootView, revealCx, revealCy, 0f, revealRadius) + .apply { + duration = REVEAL_DURATION + interpolator = DecelerateInterpolator() + } + rootView.show() + circularReveal.start() + } + private fun closeActivityWithReveal() { - if (isClosing) { - return - } - + if (isClosing) return isClosing = true - val fabSize = intent.getIntExtra(KEY_FAB_SIZE, toolbar!!.measuredHeight) - - val defaultCx = rootView.measuredWidth / 2f - val cx = - intent.getFloatExtra(KEY_FAB_X, defaultCx).toInt() + fabSize / 2 - - val defaultCy = rootView.measuredHeight / 2f - val cy = (intent.getFloatExtra(KEY_FAB_Y, defaultCy).toInt() + - toolbar!!.measuredHeight + - fabSize / 2) - - val initialRadius = max(cx, cy).toFloat() - createCircularReveal(rootView, cx, cy, initialRadius, 0f) + createCircularReveal(rootView, revealCx, revealCy, revealRadius, 0f) .apply { - duration = 300 + duration = REVEAL_DURATION interpolator = AccelerateInterpolator() onEnd { rootView.conceal() @@ -172,20 +191,6 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener { } } - private fun circularRevealActivity() { - val cx = rootView.measuredWidth / 2 - val cy = rootView.measuredHeight / 2 - val finalRadius = Math.max(cx, cy) - .toFloat() - val circularReveal = createCircularReveal(rootView, cx, cy, 0f, finalRadius) - .apply { - duration = 300 - interpolator = DecelerateInterpolator() - } - rootView.show() - circularReveal.start() - } - // Done button override fun onClick(view: View) { isClosing = true @@ -223,14 +228,14 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener { } val selectedCheckInterval = checkIntervalLayout.getSelectedCheckInterval() - val selectedValidationMode = getSelectedValidationMode() - val selectedValidationContent = getSelectedValidationContent() + val selectedValidationMode = + responseValidationMode.selectedItemPosition.indexToValidationMode() newModel = newModel.copy( checkInterval = selectedCheckInterval, lastCheck = currentTimeMillis() - selectedCheckInterval, validationMode = selectedValidationMode, - validationContent = selectedValidationContent + validationContent = selectedValidationMode.validationContent() ) rootView.scopeWhileAttached(Main) { @@ -250,25 +255,9 @@ class AddSiteActivity : AppCompatActivity(), View.OnClickListener { override fun onBackPressed() = closeActivityWithReveal() - private fun getSelectedValidationMode() = when (responseValidationMode.selectedItemPosition) { - 0 -> STATUS_CODE - 1 -> TERM_SEARCH - 2 -> JAVASCRIPT - else -> { - throw IllegalStateException( - "Unexpected validation mode index: ${responseValidationMode.selectedItemPosition}" - ) - } - } - - private fun getSelectedValidationContent() = when (responseValidationMode.selectedItemPosition) { - 0 -> null - 1 -> responseValidationSearchTerm.trimmedText() - 2 -> scriptInputLayout.getCode() - else -> { - throw IllegalStateException( - "Unexpected validation mode index: ${responseValidationMode.selectedItemPosition}" - ) - } + private fun ValidationMode.validationContent() = when (this) { + STATUS_CODE -> null + TERM_SEARCH -> responseValidationSearchTerm.trimmedText() + JAVASCRIPT -> scriptInputLayout.getCode() } } diff --git a/app/src/main/java/com/afollestad/nocknock/ui/MainActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/MainActivity.kt index 2df975c..3cceb52 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/MainActivity.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/MainActivity.kt @@ -5,8 +5,6 @@ */ package com.afollestad.nocknock.ui -import android.animation.ObjectAnimator -import android.animation.ObjectAnimator.ofFloat import android.annotation.SuppressLint import android.app.ActivityOptions.makeSceneTransitionAnimation import android.content.BroadcastReceiver @@ -15,12 +13,6 @@ import android.content.Intent import android.content.IntentFilter import android.os.Bundle import android.util.Log -import android.view.Menu -import android.view.MenuItem -import android.view.View -import android.view.View.X -import android.view.View.Y -import android.view.animation.PathInterpolator import androidx.appcompat.app.AppCompatActivity import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY @@ -40,17 +32,16 @@ import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.KEY_U import com.afollestad.nocknock.engine.statuscheck.CheckStatusManager import com.afollestad.nocknock.notifications.NockNotificationManager import com.afollestad.nocknock.utilities.ext.injector -import com.afollestad.nocknock.utilities.ext.onEnd import com.afollestad.nocknock.utilities.ext.safeRegisterReceiver import com.afollestad.nocknock.utilities.ext.safeUnregisterReceiver import com.afollestad.nocknock.utilities.ext.scopeWhileAttached import com.afollestad.nocknock.viewcomponents.ext.show import com.afollestad.nocknock.viewcomponents.ext.showOrHide -import com.afollestad.nocknock.utilities.util.MathUtil.bezierCurve -import kotlinx.android.synthetic.main.activity_main.emptyText import kotlinx.android.synthetic.main.activity_main.fab import kotlinx.android.synthetic.main.activity_main.list import kotlinx.android.synthetic.main.activity_main.rootView +import kotlinx.android.synthetic.main.activity_main.toolbar +import kotlinx.android.synthetic.main.include_empty_view.emptyText import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.async @@ -58,12 +49,11 @@ import kotlinx.coroutines.launch import javax.inject.Inject /** @author Aidan Follestad (afollestad) */ -class MainActivity : AppCompatActivity(), View.OnClickListener { +class MainActivity : AppCompatActivity() { companion object { private const val ADD_SITE_RQ = 6969 private const val VIEW_SITE_RQ = 6923 - private const val REVEAL_DURATION = 250L private fun log(message: String) { if (BuildConfig.DEBUG) { @@ -72,10 +62,6 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { } } - private var fabAnimator: ObjectAnimator? = null - private var originalFabX: Float = 0.toFloat() - private var originalFabY: Float = 0.toFloat() - private val intentReceiver = object : BroadcastReceiver() { override fun onReceive( context: Context, @@ -105,13 +91,26 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { injector().injectInto(this) setContentView(R.layout.activity_main) + toolbar.inflateMenu(R.menu.menu_main) + toolbar.setOnMenuItemClickListener { item -> + if (item.itemId == R.id.about) { + AboutDialog.show(this) + } + return@setOnMenuItemClickListener true + } + adapter = ServerAdapter(this::onSiteSelected) list.layoutManager = LinearLayoutManager(this) list.adapter = adapter list.addItemDecoration(DividerItemDecoration(this, VERTICAL)) - fab.setOnClickListener(this) + fab.setOnClickListener { + startActivityForResult( + intentToAdd(fab.x, fab.y, fab.measuredWidth), + ADD_SITE_RQ + ) + } notificationManager.createChannels() ensureCheckJobs() @@ -152,46 +151,6 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { } } - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_main, menu) - return super.onCreateOptionsMenu(menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == R.id.about) { - AboutDialog.show(this) - return true - } - return super.onOptionsItemSelected(item) - } - - // FAB clicked - override fun onClick(view: View) { - originalFabX = fab.x - originalFabY = fab.y - - fabAnimator?.cancel() - fabAnimator = ofFloat(view, X, Y, bezierCurve(fab, list)) - .apply { - interpolator = PathInterpolator(.5f, .5f) - duration = REVEAL_DURATION - onEnd { - startActivityForResult( - intentToAdd(originalFabX, originalFabY, fab.measuredWidth), - ADD_SITE_RQ - ) - fab.postDelayed( - { - fab.x = originalFabX - fab.y = originalFabY - }, - REVEAL_DURATION * 2 - ) - } - start() - } - } - override fun onActivityResult( requestCode: Int, resultCode: Int, diff --git a/app/src/main/java/com/afollestad/nocknock/ui/ViewSiteActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/ViewSiteActivity.kt index 8c3c6fb..cb6048d 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/ViewSiteActivity.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/ViewSiteActivity.kt @@ -24,12 +24,15 @@ import androidx.core.text.HtmlCompat import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.nocknock.BuildConfig import com.afollestad.nocknock.R +import com.afollestad.nocknock.data.LAST_CHECK_NONE import com.afollestad.nocknock.data.ServerModel import com.afollestad.nocknock.data.ServerStatus.CHECKING import com.afollestad.nocknock.data.ServerStatus.WAITING +import com.afollestad.nocknock.data.ValidationMode import com.afollestad.nocknock.data.ValidationMode.JAVASCRIPT import com.afollestad.nocknock.data.ValidationMode.STATUS_CODE import com.afollestad.nocknock.data.ValidationMode.TERM_SEARCH +import com.afollestad.nocknock.data.indexToValidationMode import com.afollestad.nocknock.data.textRes import com.afollestad.nocknock.engine.db.ServerModelStore import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.ACTION_STATUS_UPDATE @@ -41,12 +44,17 @@ import com.afollestad.nocknock.utilities.ext.isHttpOrHttps import com.afollestad.nocknock.utilities.ext.safeRegisterReceiver import com.afollestad.nocknock.utilities.ext.safeUnregisterReceiver import com.afollestad.nocknock.utilities.ext.scopeWhileAttached +import com.afollestad.nocknock.viewcomponents.ext.dimenFloat +import com.afollestad.nocknock.viewcomponents.ext.disable +import com.afollestad.nocknock.viewcomponents.ext.enable import com.afollestad.nocknock.viewcomponents.ext.hide import com.afollestad.nocknock.viewcomponents.ext.onItemSelected +import com.afollestad.nocknock.viewcomponents.ext.onScroll import com.afollestad.nocknock.viewcomponents.ext.show import com.afollestad.nocknock.viewcomponents.ext.showOrHide import com.afollestad.nocknock.viewcomponents.ext.trimmedText import kotlinx.android.synthetic.main.activity_viewsite.checkIntervalLayout +import kotlinx.android.synthetic.main.activity_viewsite.disableChecksButton import kotlinx.android.synthetic.main.activity_viewsite.doneBtn import kotlinx.android.synthetic.main.activity_viewsite.iconStatus import kotlinx.android.synthetic.main.activity_viewsite.inputName @@ -56,6 +64,7 @@ import kotlinx.android.synthetic.main.activity_viewsite.responseValidationMode import kotlinx.android.synthetic.main.activity_viewsite.responseValidationSearchTerm import kotlinx.android.synthetic.main.activity_viewsite.rootView import kotlinx.android.synthetic.main.activity_viewsite.scriptInputLayout +import kotlinx.android.synthetic.main.activity_viewsite.scrollView import kotlinx.android.synthetic.main.activity_viewsite.textLastCheckResult import kotlinx.android.synthetic.main.activity_viewsite.textNextCheck import kotlinx.android.synthetic.main.activity_viewsite.textUrlWarning @@ -122,6 +131,14 @@ class ViewSiteActivity : AppCompatActivity(), setOnMenuItemClickListener(this@ViewSiteActivity) } + scrollView.onScroll { + toolbar.elevation = if (it > toolbar.height / 4) { + toolbar.dimenFloat(R.dimen.default_elevation) + } else { + 0f + } + } + inputUrl.setOnFocusChangeListener { _, hasFocus -> if (!hasFocus) { val inputStr = inputUrl.text @@ -184,7 +201,7 @@ class ViewSiteActivity : AppCompatActivity(), inputName.setText(this.name) inputUrl.setText(this.url) - if (this.lastCheck == 0L) { + if (this.lastCheck == LAST_CHECK_NONE) { textLastCheckResult.setText(R.string.none) } else { val statusText = this.status.textRes() @@ -195,16 +212,8 @@ class ViewSiteActivity : AppCompatActivity(), } } - if (this.checkInterval == 0L) { - textNextCheck.setText(R.string.none_turned_off) - checkIntervalLayout.clear() - } else { - var lastCheck = this.lastCheck - if (lastCheck == 0L) { - lastCheck = currentTimeMillis() - } - textNextCheck.text = (lastCheck + this.checkInterval).formatDate() - } + textNextCheck.text = (this.lastCheck + this.checkInterval).formatDate() + checkIntervalLayout.set(this.checkInterval) responseValidationMode.setSelection(validationMode.value - 1) @@ -267,14 +276,14 @@ class ViewSiteActivity : AppCompatActivity(), } val selectedCheckInterval = checkIntervalLayout.getSelectedCheckInterval() - val selectedValidationMode = getSelectedValidationMode() - val selectedValidationContent = getSelectedValidationContent() + val selectedValidationMode = + responseValidationMode.selectedItemPosition.indexToValidationMode() currentModel = currentModel.copy( checkInterval = selectedCheckInterval, lastCheck = currentTimeMillis() - selectedCheckInterval, validationMode = selectedValidationMode, - validationContent = selectedValidationContent + validationContent = selectedValidationMode.validationContent() ) return true @@ -307,6 +316,7 @@ class ViewSiteActivity : AppCompatActivity(), R.id.refresh -> { rootView.scopeWhileAttached(Main) { launch(coroutineContext) { + disableChecksButton.disable() loadingProgress.setLoading() updateModelFromInput(false) currentModel = currentModel.copy(status = WAITING) @@ -317,6 +327,7 @@ class ViewSiteActivity : AppCompatActivity(), checkStatusManager.cancelCheck(currentModel) checkStatusManager.scheduleCheck(currentModel, rightNow = true) loadingProgress.setDone() + disableChecksButton.enable() } } return true @@ -363,25 +374,9 @@ class ViewSiteActivity : AppCompatActivity(), item.isEnabled = currentModel.status != CHECKING && currentModel.status != WAITING } - private fun getSelectedValidationMode() = when (responseValidationMode.selectedItemPosition) { - 0 -> STATUS_CODE - 1 -> TERM_SEARCH - 2 -> JAVASCRIPT - else -> { - throw IllegalStateException( - "Unexpected validation mode index: ${responseValidationMode.selectedItemPosition}" - ) - } - } - - private fun getSelectedValidationContent() = when (responseValidationMode.selectedItemPosition) { - 0 -> null - 1 -> responseValidationSearchTerm.trimmedText() - 2 -> scriptInputLayout.getCode() - else -> { - throw IllegalStateException( - "Unexpected validation mode index: ${responseValidationMode.selectedItemPosition}" - ) - } + private fun ValidationMode.validationContent() = when (this) { + STATUS_CODE -> null + TERM_SEARCH -> responseValidationSearchTerm.trimmedText() + JAVASCRIPT -> scriptInputLayout.getCode() } } diff --git a/app/src/main/res/layout/activity_addsite.xml b/app/src/main/res/layout/activity_addsite.xml index 89d3624..ff6f3e2 100644 --- a/app/src/main/res/layout/activity_addsite.xml +++ b/app/src/main/res/layout/activity_addsite.xml @@ -19,9 +19,10 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" + android:theme="@style/FlatToolbarTheme" app:navigationIcon="@drawable/ic_action_close" app:title="@string/add_site" - app:titleTextColor="?android:textColorPrimary" + app:titleTextColor="#FFFFFF" /> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8ce914d..2064711 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,5 +1,6 @@ - - + android:orientation="vertical" + > - + + + + + + + diff --git a/app/src/main/res/layout/activity_viewsite.xml b/app/src/main/res/layout/activity_viewsite.xml index b0b9fef..02b984f 100644 --- a/app/src/main/res/layout/activity_viewsite.xml +++ b/app/src/main/res/layout/activity_viewsite.xml @@ -15,16 +15,20 @@ android:orientation="vertical" > + @@ -66,7 +70,7 @@ android:id="@+id/inputName" android:layout_width="match_parent" android:layout_height="wrap_content" - android:fontFamily="sans-serif-light" + android:fontFamily="@font/lato" android:hint="@string/site_name" android:inputType="textPersonName|textCapWords|textAutoCorrect" android:singleLine="true" @@ -74,14 +78,14 @@ android:textColorHint="?android:textColorSecondary" android:textSize="@dimen/body_font_size" android:transitionName="site_name" - tools:ignore="UnusedAttribute" + tools:ignore="Autofill,UnusedAttribute" /> + + diff --git a/app/src/main/res/layout/include_empty_view.xml b/app/src/main/res/layout/include_empty_view.xml new file mode 100644 index 0000000..1adf674 --- /dev/null +++ b/app/src/main/res/layout/include_empty_view.xml @@ -0,0 +1,14 @@ + + diff --git a/app/src/main/res/layout/list_item_server.xml b/app/src/main/res/layout/list_item_server.xml index fd0863e..e1e8728 100644 --- a/app/src/main/res/layout/list_item_server.xml +++ b/app/src/main/res/layout/list_item_server.xml @@ -43,7 +43,7 @@ android:layout_centerVertical="true" android:layout_marginEnd="@dimen/content_inset_half" android:layout_toStartOf="@+id/textInterval" - android:fontFamily="sans-serif-medium" + android:fontFamily="@font/lato" android:singleLine="true" android:textColor="?android:textColorPrimary" android:textSize="@dimen/title_font_size" @@ -57,7 +57,7 @@ android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:layout_centerVertical="true" - android:fontFamily="sans-serif-light" + android:fontFamily="@font/lato_light" android:singleLine="true" android:textColor="?android:textColorSecondary" android:textSize="@dimen/caption_font_size" @@ -71,7 +71,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/list_text_spacing" - android:fontFamily="sans-serif" + android:fontFamily="@font/lato" android:singleLine="true" android:textColor="?android:textColorSecondary" android:textSize="@dimen/body_font_size" @@ -84,7 +84,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/list_text_spacing" - android:fontFamily="sans-serif" + android:fontFamily="@font/lato" android:singleLine="true" android:textColor="?android:textColorSecondary" android:textSize="@dimen/body_font_size" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 50f16a5..c0fa7eb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - + - Nock Nock (BETA) + Nock Nock No sites added! @@ -29,7 +29,8 @@ Remove Site %1$s from your sites?]]> Remove - Save + Save Changes + Disable Automatic Checks View Site Last Check Result Next Check @@ -39,13 +40,23 @@ Refresh Status - Warning: this app checks for server availability with HTTP requests. It\'s recommended that you use an HTTP URL. - + Warning: this app checks for server availability with HTTP requests. It\'s recommended that you + use an HTTP URL. + Response Validation Mode Search term… - The HTTP status code is checked. If it\'s a successful status code, the site passes the check. - The status code check is done first. If it\'s successful, the response body is checked. If it contains your search term, the site passes the check. - The status code check is done first. If it\'s successful, the response body is passed to the JavaScript function above. If the function returns true, the site passes the check. Throw an exception to pass custom error messages to Nock Nock. + + The HTTP status code is checked. If it\'s a successful status code, the site passes the check. + + + The status code check is done first. If it\'s successful, the response body is checked. + If it contains your search term, the site passes the check. + + + The status code check is done first. If it\'s successful, the response body is passed to the + JavaScript function above. If the function returns true, the site passes the check. Throw an + exception to pass custom error messages to Nock Nock. + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 781d2c4..1df7c54 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,6 +1,6 @@ - + + + + + + + + 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 a0eac69..df10e4e 100644 --- a/data/src/main/java/com/afollestad/nocknock/data/ServerModel.kt +++ b/data/src/main/java/com/afollestad/nocknock/data/ServerModel.kt @@ -12,17 +12,21 @@ import com.afollestad.nocknock.utilities.ext.timeString import java.io.Serializable import java.lang.System.currentTimeMillis +const val CHECK_INTERVAL_UNSET = -1L +const val LAST_CHECK_NONE = -1L + /** @author Aidan Follestad (afollestad)*/ data class ServerModel( var id: Int = 0, - val name: String = "Unknown", - val url: String = "Unknown", + val name: String, + val url: String, val status: ServerStatus = OK, - val checkInterval: Long = 0, - val lastCheck: Long = 0, + val checkInterval: Long = CHECK_INTERVAL_UNSET, + val lastCheck: Long = LAST_CHECK_NONE, val reason: String? = null, val validationMode: ValidationMode, - val validationContent: String? = null + val validationContent: String? = null, + val disabled: Boolean = false ) : Serializable { companion object { @@ -36,8 +40,9 @@ data class ServerModel( const val COLUMN_REASON = "reason" const val COLUMN_VALIDATION_MODE = "validation_mode" const val COLUMN_VALIDATION_CONTENT = "validation_content" + const val COLUMN_DISABLED = "disabled" - const val DEFAULT_SORT_ORDER = "$COLUMN_NAME ASC" + const val DEFAULT_SORT_ORDER = "$COLUMN_NAME ASC, $COLUMN_DISABLED DESC" fun pull(cursor: Cursor): ServerModel { return ServerModel( @@ -51,7 +56,8 @@ data class ServerModel( validationMode = cursor.getInt( cursor.getColumnIndex(COLUMN_VALIDATION_MODE) ).toValidationMode(), - validationContent = cursor.getString(cursor.getColumnIndex(COLUMN_VALIDATION_CONTENT)) + validationContent = cursor.getString(cursor.getColumnIndex(COLUMN_VALIDATION_CONTENT)), + disabled = cursor.getInt(cursor.getColumnIndex(COLUMN_DISABLED)) == 1 ) } } @@ -73,5 +79,6 @@ data class ServerModel( put(COLUMN_REASON, reason) put(COLUMN_VALIDATION_MODE, validationMode.value) put(COLUMN_VALIDATION_CONTENT, validationContent) + put(COLUMN_DISABLED, disabled) } } diff --git a/data/src/main/java/com/afollestad/nocknock/data/ValidationMode.kt b/data/src/main/java/com/afollestad/nocknock/data/ValidationMode.kt index e209931..f9a1586 100644 --- a/data/src/main/java/com/afollestad/nocknock/data/ValidationMode.kt +++ b/data/src/main/java/com/afollestad/nocknock/data/ValidationMode.kt @@ -19,7 +19,16 @@ enum class ValidationMode(val value: Int) { JAVASCRIPT.value -> JAVASCRIPT else -> throw IllegalArgumentException("Unknown validationMode: $value") } + + fun fromIndex(index: Int) = when (index) { + 0 -> STATUS_CODE + 1 -> TERM_SEARCH + 2 -> JAVASCRIPT + else -> throw IllegalArgumentException("Index out of range: $index") + } } } fun Int.toValidationMode() = ValidationMode.fromValue(this) + +fun Int.indexToValidationMode() = ValidationMode.fromIndex(this) 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 7b8bcfc..6996f32 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 @@ -20,7 +20,8 @@ private const val SQL_CREATE_ENTRIES = "${ServerModel.COLUMN_LAST_CHECK} INTEGER," + "${ServerModel.COLUMN_REASON} TEXT," + "${ServerModel.COLUMN_VALIDATION_MODE} INTEGER," + - "${ServerModel.COLUMN_VALIDATION_CONTENT} TEXT)" + "${ServerModel.COLUMN_VALIDATION_CONTENT} TEXT," + + "${ServerModel.COLUMN_DISABLED} INTEGER)" private const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS ${ServerModel.TABLE_NAME}" @@ -29,7 +30,7 @@ class ServerModelDbHelper(context: Context) : SQLiteOpenHelper( context, DATABASE_NAME, null, DATABASE_VERSION ) { companion object { - const val DATABASE_VERSION = 1 + const val DATABASE_VERSION = 2 const val DATABASE_NAME = "ServerModels.db" } diff --git a/utilities/src/main/java/com/afollestad/nocknock/utilities/ext/DateExt.kt b/utilities/src/main/java/com/afollestad/nocknock/utilities/ext/DateExt.kt index 6e41212..d29c261 100644 --- a/utilities/src/main/java/com/afollestad/nocknock/utilities/ext/DateExt.kt +++ b/utilities/src/main/java/com/afollestad/nocknock/utilities/ext/DateExt.kt @@ -10,6 +10,9 @@ import java.util.Date import java.util.Locale fun Long.formatDate(): String { + if (this <= 0) { + return "(None)" + } val df = SimpleDateFormat("MMMM dd, hh:mm:ss a", Locale.getDefault()) return df.format(Date(this)) } diff --git a/utilities/src/main/java/com/afollestad/nocknock/utilities/util/MathUtil.kt b/utilities/src/main/java/com/afollestad/nocknock/utilities/util/MathUtil.kt deleted file mode 100644 index 7c5a98f..0000000 --- a/utilities/src/main/java/com/afollestad/nocknock/utilities/util/MathUtil.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed under Apache-2.0 - * - * Designed and developed by Aidan Follestad (@afollestad) - */ -package com.afollestad.nocknock.utilities.util - -import android.graphics.Path -import android.view.View - -/** @author Aidan Follestad (afollestad) */ -object MathUtil { - - fun bezierCurve( - targetView: View, - rootView: View - ): Path { - val fabCenterX = (targetView.x + targetView.measuredWidth / 2).toInt() - val fabCenterY = (targetView.y + targetView.measuredHeight / 2).toInt() - - val endCenterX = rootView.measuredWidth / 2 - targetView.measuredWidth / 2 - val endCenterY = rootView.measuredHeight / 2 - targetView.measuredHeight / 2 - - val halfX = (fabCenterX - endCenterX) / 2 - val halfY = (fabCenterY - endCenterY) / 2 - - var controlX = endCenterX + halfX - var controlY = endCenterY + halfY - - controlY -= halfY - controlX += halfX - - val path = Path() - path.moveTo(targetView.x, targetView.y) - path.quadTo( - controlX.toFloat(), - controlY.toFloat(), - endCenterX.toFloat(), - endCenterY.toFloat() - ) - - return path - } -} diff --git a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/CheckIntervalLayout.kt b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/CheckIntervalLayout.kt index 694924b..1b35548 100644 --- a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/CheckIntervalLayout.kt +++ b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/CheckIntervalLayout.kt @@ -74,11 +74,6 @@ class CheckIntervalLayout( } } - fun clear() { - input.setText("") - spinner.setSelection(0) - } - @CheckResult fun getSelectedCheckInterval(): Long { val intervalInput = input.textAsLong() val spinnerPos = spinner.selectedItemPosition diff --git a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/LoadingIndicatorFrame.kt b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/LoadingIndicatorFrame.kt index 731f2bc..7c2d0db 100644 --- a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/LoadingIndicatorFrame.kt +++ b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/LoadingIndicatorFrame.kt @@ -27,6 +27,8 @@ class LoadingIndicatorFrame( setBackgroundColor(ContextCompat.getColor(context, R.color.loading_indicator_frame_background)) hide() // hide self by default inflate(context, R.layout.loading_indicator_frame, this) + isClickable = true + isFocusable = true } fun setLoading() { diff --git a/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/ext/ScrollViewExt.kt b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/ext/ScrollViewExt.kt new file mode 100644 index 0000000..1c45cd5 --- /dev/null +++ b/viewcomponents/src/main/java/com/afollestad/nocknock/viewcomponents/ext/ScrollViewExt.kt @@ -0,0 +1,11 @@ +/* + * Licensed under Apache-2.0 + * + * Designed and developed by Aidan Follestad (@afollestad) + */ +package com.afollestad.nocknock.viewcomponents.ext + +import android.widget.ScrollView + +fun ScrollView.onScroll(cb: (y: Int) -> Unit) = + viewTreeObserver.addOnScrollChangedListener { cb(scrollY) } 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 24691d0..5f86e74 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 @@ -24,6 +24,14 @@ fun View.hide() { visibility = GONE } +fun View.enable() { + isEnabled = true +} + +fun View.disable() { + isEnabled = false +} + fun View.showOrHide(show: Boolean) = if (show) show() else hide() fun View.onLayout(cb: () -> Unit) { diff --git a/viewcomponents/src/main/res/font/fira_mono.xml b/viewcomponents/src/main/res/font/fira_mono.xml new file mode 100644 index 0000000..7975d1b --- /dev/null +++ b/viewcomponents/src/main/res/font/fira_mono.xml @@ -0,0 +1,7 @@ + + + diff --git a/viewcomponents/src/main/res/font/lato.xml b/viewcomponents/src/main/res/font/lato.xml new file mode 100644 index 0000000..015fa0c --- /dev/null +++ b/viewcomponents/src/main/res/font/lato.xml @@ -0,0 +1,7 @@ + + + diff --git a/viewcomponents/src/main/res/font/lato_black.xml b/viewcomponents/src/main/res/font/lato_black.xml new file mode 100644 index 0000000..dfabc45 --- /dev/null +++ b/viewcomponents/src/main/res/font/lato_black.xml @@ -0,0 +1,7 @@ + + + diff --git a/viewcomponents/src/main/res/font/lato_light.xml b/viewcomponents/src/main/res/font/lato_light.xml new file mode 100644 index 0000000..e502755 --- /dev/null +++ b/viewcomponents/src/main/res/font/lato_light.xml @@ -0,0 +1,7 @@ + + + diff --git a/viewcomponents/src/main/res/layout/check_interval_layout.xml b/viewcomponents/src/main/res/layout/check_interval_layout.xml index 05a8948..227c408 100644 --- a/viewcomponents/src/main/res/layout/check_interval_layout.xml +++ b/viewcomponents/src/main/res/layout/check_interval_layout.xml @@ -5,13 +5,14 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + tools:parentTag="android.widget.LinearLayout" > diff --git a/viewcomponents/src/main/res/layout/list_item_spinner.xml b/viewcomponents/src/main/res/layout/list_item_spinner.xml index b6170b9..8469e57 100644 --- a/viewcomponents/src/main/res/layout/list_item_spinner.xml +++ b/viewcomponents/src/main/res/layout/list_item_spinner.xml @@ -3,7 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="@dimen/button_height" - android:fontFamily="sans-serif-light" + android:fontFamily="@font/lato" android:gravity="center_vertical|start" android:textColor="?android:textColorPrimary" android:textSize="@dimen/body_font_size" diff --git a/viewcomponents/src/main/res/layout/list_item_spinner_dropdown.xml b/viewcomponents/src/main/res/layout/list_item_spinner_dropdown.xml index 4d99ee8..141b174 100644 --- a/viewcomponents/src/main/res/layout/list_item_spinner_dropdown.xml +++ b/viewcomponents/src/main/res/layout/list_item_spinner_dropdown.xml @@ -3,7 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="@dimen/button_height" - android:fontFamily="sans-serif-light" + android:fontFamily="@font/lato" android:gravity="center_vertical|start" android:paddingLeft="@dimen/content_inset" android:paddingRight="@dimen/content_inset" diff --git a/viewcomponents/src/main/res/layout/loading_indicator_frame.xml b/viewcomponents/src/main/res/layout/loading_indicator_frame.xml index 8584f55..af0e65b 100644 --- a/viewcomponents/src/main/res/layout/loading_indicator_frame.xml +++ b/viewcomponents/src/main/res/layout/loading_indicator_frame.xml @@ -4,12 +4,10 @@ android:layout_width="match_parent" android:layout_height="match_parent" > - - diff --git a/viewcomponents/src/main/res/values/dimens.xml b/viewcomponents/src/main/res/values/dimens.xml index 09514e8..2601771 100644 --- a/viewcomponents/src/main/res/values/dimens.xml +++ b/viewcomponents/src/main/res/values/dimens.xml @@ -9,6 +9,7 @@ 12dp 16dp 24dp + 32dp 14sp 14sp diff --git a/viewcomponents/src/main/res/values/font_certs.xml b/viewcomponents/src/main/res/values/font_certs.xml new file mode 100644 index 0000000..d2226ac --- /dev/null +++ b/viewcomponents/src/main/res/values/font_certs.xml @@ -0,0 +1,17 @@ + + + + @array/com_google_android_gms_fonts_certs_dev + @array/com_google_android_gms_fonts_certs_prod + + + + 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 + + + diff --git a/viewcomponents/src/main/res/values/preloaded_fonts.xml b/viewcomponents/src/main/res/values/preloaded_fonts.xml new file mode 100644 index 0000000..6a94ecb --- /dev/null +++ b/viewcomponents/src/main/res/values/preloaded_fonts.xml @@ -0,0 +1,9 @@ + + + + @font/fira_mono + @font/lato + @font/lato_black + @font/lato_light + +