WIP use custom fonts, cleanup layouts, etc.

This commit is contained in:
Aidan Follestad 2018-11-30 18:28:09 -08:00
parent 77a98b161b
commit ef73245831
32 changed files with 336 additions and 270 deletions

View file

@ -52,6 +52,9 @@
</intent-filter>
</receiver>
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts"/>
</application>

View file

@ -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<Int>()
private var revealCy by notNull<Int>()
private var revealRadius by notNull<Float>()
@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()
}
}

View file

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

View file

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

View file

@ -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"
/>
<ScrollView
@ -51,7 +52,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:textColor="?android:textColorPrimary"
@ -74,7 +75,7 @@
android:id="@+id/inputUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:fontFamily="@font/lato"
android:hint="@string/site_url"
android:inputType="textUri"
android:textColor="?android:textColorPrimary"
@ -89,7 +90,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/list_text_spacing"
android:fontFamily="sans-serif-light"
android:fontFamily="@font/lato_light"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/caption_font_size"
android:visibility="gone"
@ -106,11 +107,11 @@
/>
<TextView
android:id="@+id/responseValidation"
android:id="@+id/responseValidationLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset"
android:fontFamily="sans-serif"
android:fontFamily="@font/lato"
android:text="@string/response_validation_mode"
android:textColor="?colorAccent"
android:textSize="@dimen/caption_font_size"
@ -130,11 +131,11 @@
android:layout_marginLeft="-4dp"
android:layout_marginRight="-4dp"
android:layout_marginTop="-4dp"
android:fontFamily="sans-serif-light"
android:fontFamily="@font/lato_light"
android:hint="@string/search_term"
android:textSize="@dimen/body_font_size"
android:visibility="gone"
tools:ignore="Autofill"
tools:ignore="Autofill,TextFields"
/>
<com.afollestad.nocknock.viewcomponents.JavaScriptInputLayout
@ -151,7 +152,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset_half"
android:fontFamily="sans-serif-light"
android:fontFamily="@font/lato_light"
android:lineSpacingMultiplier="1.2"
android:text="@string/validation_mode_status_desc"
android:textSize="@dimen/body_font_size"
@ -161,7 +162,7 @@
android:id="@+id/doneBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset"
android:layout_marginTop="@dimen/content_inset_double"
android:text="@string/done"
style="@style/AccentButton"
/>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootView"
@ -8,38 +9,43 @@
tools:context=".ui.MainActivity"
>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
/>
android:orientation="vertical"
>
<TextView
android:id="@+id/emptyText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginBottom="@dimen/content_inset"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:text="@string/no_sites_added"
android:textSize="@dimen/empty_text_size"
android:textStyle="italic"
/>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/MainToolbarTheme"
style="@style/MainToolbarStyle"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
/>
</LinearLayout>
<include layout="@layout/include_empty_view"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="@dimen/content_inset"
android:src="@drawable/ic_add"
app:backgroundTint="?colorAccent"
app:elevation="@dimen/fab_elevation"
app:fabSize="normal"
app:pressedTranslationZ="@dimen/fab_elevation_pressed"
app:rippleColor="#40ffffff"
app:useCompatPadding="true"
/>
</FrameLayout>

View file

@ -15,16 +15,20 @@
android:orientation="vertical"
>
<!-- Background is applied again here so programmatic elevation works -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorPrimary"
android:theme="@style/FlatToolbarTheme"
app:navigationIcon="@drawable/ic_action_close"
app:title="@string/view_site"
app:titleTextColor="?android:textColorPrimary"
/>
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
@ -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"
/>
<EditText
android:id="@+id/inputUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:fontFamily="@font/lato"
android:hint="@string/site_url"
android:inputType="textUri"
android:singleLine="true"
@ -89,7 +93,7 @@
android:textColorHint="?android:textColorSecondary"
android:textSize="@dimen/body_font_size"
android:transitionName="site_url"
tools:ignore="UnusedAttribute"
tools:ignore="Autofill,UnusedAttribute"
/>
<TextView
@ -99,7 +103,7 @@
android:layout_marginEnd="4dp"
android:layout_marginStart="4dp"
android:layout_marginTop="@dimen/list_text_spacing"
android:fontFamily="sans-serif-light"
android:fontFamily="@font/lato_light"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/caption_font_size"
android:visibility="gone"
@ -132,11 +136,11 @@
/>
<TextView
android:id="@+id/responseValidation"
android:id="@+id/responseValidationLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset"
android:fontFamily="sans-serif"
android:fontFamily="@font/lato"
android:text="@string/response_validation_mode"
android:textColor="?colorAccent"
android:textSize="@dimen/caption_font_size"
@ -156,7 +160,7 @@
android:layout_marginLeft="-4dp"
android:layout_marginRight="-4dp"
android:layout_marginTop="-4dp"
android:fontFamily="sans-serif-light"
android:fontFamily="@font/lato_light"
android:hint="@string/search_term"
android:textSize="@dimen/body_font_size"
android:visibility="gone"
@ -177,7 +181,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_inset_half"
android:fontFamily="sans-serif-light"
android:fontFamily="@font/lato"
android:lineSpacingMultiplier="1.2"
android:text="@string/validation_mode_status_desc"
android:textSize="@dimen/body_font_size"
@ -199,7 +203,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:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_text_size"
tools:text="Everything checks out!"
@ -219,7 +223,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:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_text_size"
tools:text="In 2 hours"
@ -229,11 +233,20 @@
android:id="@+id/doneBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset_more"
android:text="@string/save"
android:layout_marginTop="@dimen/content_inset_double"
android:text="@string/save_changes"
style="@style/AccentButton"
/>
<com.google.android.material.button.MaterialButton
android:id="@+id/disableChecksButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset_half"
android:text="@string/disable_automatic_checks"
style="@style/PrimaryDarkButton"
/>
</LinearLayout>
</ScrollView>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/emptyText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginBottom="@dimen/content_inset"
android:fontFamily="@font/lato_light"
android:gravity="center"
android:text="@string/no_sites_added"
android:textSize="@dimen/empty_text_size"
android:textStyle="italic"
/>

View file

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

View file

@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<string name="app_name" tools:ignore="Typos">Nock Nock (BETA)</string>
<string name="app_name">Nock Nock</string>
<string name="no_sites_added">No sites added!</string>
@ -29,7 +29,8 @@
<string name="remove_site">Remove Site</string>
<string name="remove_site_prompt"><![CDATA[Remove <b>%1$s</b> from your sites?]]></string>
<string name="remove">Remove</string>
<string name="save">Save</string>
<string name="save_changes">Save Changes</string>
<string name="disable_automatic_checks">Disable Automatic Checks</string>
<string name="view_site">View Site</string>
<string name="last_check_result">Last Check Result</string>
<string name="next_check">Next Check</string>
@ -39,13 +40,23 @@
<string name="refresh_status">Refresh Status</string>
<string name="warning_http_url">
Warning: this app checks for server availability with HTTP requests. It\'s recommended that you use an HTTP URL.
</string>
Warning: this app checks for server availability with HTTP requests. It\'s recommended that you
use an HTTP URL.
</string>
<string name="response_validation_mode">Response Validation Mode</string>
<string name="search_term">Search term…</string>
<string name="validation_mode_status_desc">The HTTP status code is checked. If it\'s a successful status code, the site passes the check.</string>
<string name="validation_mode_term_desc">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.</string>
<string name="validation_mode_javascript_desc">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.</string>
<string name="validation_mode_status_desc">
The HTTP status code is checked. If it\'s a successful status code, the site passes the check.
</string>
<string name="validation_mode_term_desc">
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.
</string>
<string name="validation_mode_javascript_desc">
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.
</string>
</resources>

View file

@ -1,6 +1,6 @@
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
@ -27,9 +27,38 @@
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<style name="MainToolbarTheme" parent="@style/Theme.MaterialComponents">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:fontFamily">@font/lato_black</item>
</style>
<style name="MainToolbarStyle" parent="@style/Widget.MaterialComponents.Toolbar">
<item name="android:background">?colorPrimary</item>
<item name="android:elevation">@dimen/default_elevation</item>
<item name="title">@string/app_name</item>
<item name="titleTextColor">#FFFFFF</item>
<item name="popupTheme">@style/Theme.MaterialComponents.Light.DarkActionBar</item>
</style>
<style name="FlatToolbarTheme" parent="@style/Theme.MaterialComponents">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:fontFamily">@font/lato_black</item>
</style>
<style name="AccentButton" parent="Widget.MaterialComponents.Button">
<item name="android:textColor">#fff</item>
<item name="android:colorButtonNormal">@color/colorAccent</item>
<item name="backgroundTint">@color/colorAccent</item>
<item name="android:fontFamily">@font/lato</item>
</style>
<style name="PrimaryDarkButton" parent="Widget.MaterialComponents.Button">
<item name="android:textColor">#fff</item>
<item name="backgroundTint">@color/colorPrimaryDark</item>
<item name="android:fontFamily">@font/lato</item>
</style>
</resources>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="Fira Mono"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="Lato"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="name=Lato&amp;weight=900"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="name=Lato&amp;weight=300"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View file

@ -5,13 +5,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:parentTag="android.widget.LinearLayout"
>
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:fontFamily="@font/lato"
android:text="@string/check_interval"
android:textColor="?colorAccent"
android:textSize="@dimen/caption_font_size"
@ -32,7 +33,7 @@
android:layout_marginEnd="@dimen/content_inset_half"
android:layout_marginStart="-4dp"
android:layout_weight="1"
android:fontFamily="sans-serif-light"
android:fontFamily="@font/lato_light"
android:hint="0"
android:inputType="number"
android:maxLength="6"

View file

@ -16,7 +16,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="serif-monospace"
android:fontFamily="@font/fira_mono"
android:lineSpacingMultiplier="1.4"
android:singleLine="true"
android:text="@string/function_declaration"
@ -28,7 +28,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:fontFamily="serif-monospace"
android:fontFamily="@font/fira_mono"
android:gravity="top"
android:inputType="textMultiLine"
android:lineSpacingMultiplier="1.6"
@ -45,7 +45,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="serif-monospace"
android:fontFamily="@font/fira_mono"
android:text="@string/function_end"
android:textSize="@dimen/code_font_size"
/>

View file

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

View file

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

View file

@ -4,12 +4,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
style="?android:progressBarStyleLarge"
/>
</merge>

View file

@ -9,6 +9,7 @@
<dimen name="content_inset_less">12dp</dimen>
<dimen name="content_inset">16dp</dimen>
<dimen name="content_inset_more">24dp</dimen>
<dimen name="content_inset_double">32dp</dimen>
<dimen name="code_font_size">14sp</dimen>
<dimen name="body_font_size">14sp</dimen>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="com_google_android_gms_fonts_certs">
<item>@array/com_google_android_gms_fonts_certs_dev</item>
<item>@array/com_google_android_gms_fonts_certs_prod</item>
</array>
<string-array name="com_google_android_gms_fonts_certs_dev">
<item>
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
</item>
</string-array>
<string-array name="com_google_android_gms_fonts_certs_prod">
<item>
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
</item>
</string-array>
</resources>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="preloaded_fonts" translatable="false">
<item>@font/fira_mono</item>
<item>@font/lato</item>
<item>@font/lato_black</item>
<item>@font/lato_light</item>
</array>
</resources>