mirror of
https://github.com/afollestad/nock-nock.git
synced 2025-08-25 11:48:21 +00:00
Tag system, resolves #13
This commit is contained in:
parent
2756fc9fc7
commit
002149cd3f
26 changed files with 326 additions and 20 deletions
|
@ -66,9 +66,6 @@ dependencies {
|
|||
// afollestad
|
||||
implementation 'com.afollestad.material-dialogs:core:' + versions.materialDialogs
|
||||
|
||||
// Misc
|
||||
implementation 'com.github.okdroid:checkablechipview:' + versions.chipView
|
||||
|
||||
// Debugging
|
||||
implementation 'com.jakewharton.timber:timber:' + versions.timber
|
||||
implementation("com.crashlytics.sdk.android:crashlytics:${versions.fabric}") {
|
||||
|
|
115
app/src/main/java/com/afollestad/nocknock/adapter/TagAdapter.kt
Normal file
115
app/src/main/java/com/afollestad/nocknock/adapter/TagAdapter.kt
Normal file
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* Designed and developed by Aidan Follestad (@afollestad)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.afollestad.nocknock.adapter
|
||||
|
||||
import android.graphics.Color.WHITE
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.OnClickListener
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import com.afollestad.nocknock.R
|
||||
import com.afollestad.nocknock.adapter.TagAdapter.TagViewHolder
|
||||
import kotlinx.android.synthetic.main.list_item_tag.view.chip
|
||||
|
||||
typealias TagsListener = (tags: List<String>) -> Unit
|
||||
|
||||
/** @author Aidan Follestad (@afollestad) */
|
||||
class TagAdapter(
|
||||
private val listener: TagsListener
|
||||
) : RecyclerView.Adapter<TagViewHolder>() {
|
||||
|
||||
private val tags = mutableListOf<String>()
|
||||
private val checked = mutableListOf<Int>()
|
||||
|
||||
fun set(tags: List<String>) {
|
||||
this.tags.run {
|
||||
clear()
|
||||
addAll(tags)
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun toggleChecked(index: Int) {
|
||||
if (checked.contains(index)) {
|
||||
checked.remove(index)
|
||||
} else {
|
||||
checked.add(index)
|
||||
}
|
||||
notifyItemChanged(index)
|
||||
listener.invoke(getCheckedTags())
|
||||
}
|
||||
|
||||
private fun getCheckedTags(): List<String> {
|
||||
return mutableListOf<String>().apply {
|
||||
checked.forEach { index -> add(tags[index]) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): TagViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.list_item_tag, parent, false)
|
||||
return TagViewHolder(view, this)
|
||||
}
|
||||
|
||||
override fun getItemCount() = tags.size
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: TagViewHolder,
|
||||
position: Int
|
||||
) {
|
||||
holder.bind(tags[position], checked.contains(position))
|
||||
}
|
||||
|
||||
/** @author Aidan Follestad (@afollestad) */
|
||||
class TagViewHolder(
|
||||
itemView: View,
|
||||
private val adapter: TagAdapter
|
||||
) : ViewHolder(itemView), OnClickListener {
|
||||
|
||||
override fun onClick(v: View) = adapter.toggleChecked(adapterPosition)
|
||||
|
||||
init {
|
||||
itemView.setOnClickListener(this)
|
||||
}
|
||||
|
||||
fun bind(
|
||||
name: String,
|
||||
checked: Boolean
|
||||
) = itemView.chip.run {
|
||||
text = name
|
||||
setTextColor(
|
||||
if (checked) {
|
||||
WHITE
|
||||
} else {
|
||||
ContextCompat.getColor(itemView.context, R.color.unchecked_chip_text)
|
||||
}
|
||||
)
|
||||
setBackgroundResource(
|
||||
if (checked) {
|
||||
R.drawable.checked_chip_selector
|
||||
} else {
|
||||
R.drawable.unchecked_chip_selector
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import com.afollestad.nocknock.viewcomponents.livedata.toViewVisibility
|
|||
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.inputTags
|
||||
import kotlinx.android.synthetic.main.activity_addsite.inputUrl
|
||||
import kotlinx.android.synthetic.main.activity_addsite.loadingProgress
|
||||
import kotlinx.android.synthetic.main.activity_addsite.responseTimeoutInput
|
||||
|
@ -62,6 +63,9 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
|||
viewModel.onNameError()
|
||||
.toViewError(this, inputName)
|
||||
|
||||
// Tags
|
||||
inputTags.attachLiveData(this, viewModel.tags)
|
||||
|
||||
// Url
|
||||
inputUrl.attachLiveData(this, viewModel.url)
|
||||
viewModel.onUrlError()
|
||||
|
|
|
@ -56,6 +56,7 @@ class AddSiteViewModel(
|
|||
|
||||
// Public properties
|
||||
val name = MutableLiveData<String>()
|
||||
val tags = MutableLiveData<String>()
|
||||
val url = MutableLiveData<String>()
|
||||
val timeout = MutableLiveData<Int>()
|
||||
val validationMode = MutableLiveData<ValidationMode>()
|
||||
|
@ -74,6 +75,7 @@ class AddSiteViewModel(
|
|||
checkIntervalUnit.value = MINUTE
|
||||
retryPolicyMinutes.value = 0
|
||||
retryPolicyMinutes.value = 0
|
||||
tags.value = ""
|
||||
}
|
||||
|
||||
// Private properties
|
||||
|
@ -224,6 +226,8 @@ class AddSiteViewModel(
|
|||
return null
|
||||
}
|
||||
|
||||
val cleanedTags = tags.value?.split(',')?.joinToString { it.trim() } ?: ""
|
||||
|
||||
val newSettings = SiteSettings(
|
||||
validationIntervalMs = getCheckIntervalMs(),
|
||||
validationMode = validationMode.value!!,
|
||||
|
@ -252,6 +256,7 @@ class AddSiteViewModel(
|
|||
id = 0,
|
||||
name = name.value!!.trim(),
|
||||
url = url.value!!.trim(),
|
||||
tags = cleanedTags,
|
||||
settings = newSettings,
|
||||
lastResult = newLastResult,
|
||||
retryPolicy = newRetryPolicy
|
||||
|
|
|
@ -21,11 +21,13 @@ import androidx.lifecycle.Observer
|
|||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.list.listItems
|
||||
import com.afollestad.materialdialogs.list.listItemsSingleChoice
|
||||
import com.afollestad.nocknock.R
|
||||
import com.afollestad.nocknock.adapter.SiteAdapter
|
||||
import com.afollestad.nocknock.adapter.TagAdapter
|
||||
import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver
|
||||
import com.afollestad.nocknock.data.model.Site
|
||||
import com.afollestad.nocknock.dialogs.AboutDialog
|
||||
|
@ -43,6 +45,7 @@ import kotlinx.android.synthetic.main.include_app_bar.toolbar
|
|||
import kotlinx.android.synthetic.main.include_empty_view.emptyText
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import kotlinx.android.synthetic.main.activity_main.tags_list as tagsList
|
||||
|
||||
/** @author Aidan Follestad (@afollestad) */
|
||||
class MainActivity : DarkModeSwitchActivity() {
|
||||
|
@ -53,6 +56,7 @@ class MainActivity : DarkModeSwitchActivity() {
|
|||
internal val viewModel by viewModel<MainViewModel>()
|
||||
|
||||
private lateinit var siteAdapter: SiteAdapter
|
||||
private lateinit var tagAdapter: TagAdapter
|
||||
|
||||
private val statusUpdateReceiver by lazy {
|
||||
StatusUpdateIntentReceiver(application, intentProvider) {
|
||||
|
@ -76,6 +80,10 @@ class MainActivity : DarkModeSwitchActivity() {
|
|||
.observe(this, Observer { siteAdapter.set(it) })
|
||||
viewModel.onEmptyTextVisibility()
|
||||
.toViewVisibility(this, emptyText)
|
||||
viewModel.onTags()
|
||||
.observe(this, Observer { tagAdapter.set(it) })
|
||||
viewModel.onTagsListVisibility()
|
||||
.toViewVisibility(this, tagsList)
|
||||
loadingProgress.observe(this, viewModel.onIsLoading())
|
||||
|
||||
processIntent(intent)
|
||||
|
@ -97,12 +105,18 @@ class MainActivity : DarkModeSwitchActivity() {
|
|||
}
|
||||
|
||||
siteAdapter = SiteAdapter(this::onSiteSelected)
|
||||
|
||||
list.run {
|
||||
layoutManager = LinearLayoutManager(this@MainActivity)
|
||||
adapter = siteAdapter
|
||||
addItemDecoration(DividerItemDecoration(this@MainActivity, VERTICAL))
|
||||
}
|
||||
|
||||
tagAdapter = TagAdapter(viewModel::onTagSelection)
|
||||
tagsList.run {
|
||||
layoutManager = LinearLayoutManager(this@MainActivity, HORIZONTAL, false)
|
||||
adapter = tagAdapter
|
||||
}
|
||||
|
||||
fab.setOnClickListener { addSite() }
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ class MainViewModel(
|
|||
private val sites = MutableLiveData<List<Site>>()
|
||||
private val isLoading = MutableLiveData<Boolean>()
|
||||
private val emptyTextVisibility = MutableLiveData<Boolean>()
|
||||
private val tags = MutableLiveData<List<String>>()
|
||||
private val tagsListVisibility = MutableLiveData<Boolean>()
|
||||
|
||||
@CheckResult fun onSites(): LiveData<List<Site>> = sites
|
||||
|
||||
|
@ -51,8 +53,14 @@ class MainViewModel(
|
|||
|
||||
@CheckResult fun onEmptyTextVisibility(): LiveData<Boolean> = emptyTextVisibility
|
||||
|
||||
@CheckResult fun onTags(): LiveData<List<String>> = tags
|
||||
|
||||
@CheckResult fun onTagsListVisibility(): LiveData<Boolean> = tagsListVisibility
|
||||
|
||||
@OnLifecycleEvent(ON_RESUME)
|
||||
fun onResume() = loadSites()
|
||||
fun onResume() = loadSites(emptyList())
|
||||
|
||||
fun onTagSelection(tags: List<String>) = loadSites(tags)
|
||||
|
||||
fun postSiteUpdate(model: Site) {
|
||||
val currentSites = sites.value ?: return
|
||||
|
@ -94,21 +102,26 @@ class MainViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadSites() {
|
||||
private fun loadSites(forTags: List<String>) {
|
||||
scope.launch {
|
||||
notificationManager.cancelStatusNotifications()
|
||||
sites.value = listOf()
|
||||
emptyTextVisibility.value = false
|
||||
isLoading.value = true
|
||||
|
||||
val result = withContext(ioDispatcher) {
|
||||
database.allSites()
|
||||
database.allSites(forTags)
|
||||
}
|
||||
|
||||
sites.value = result
|
||||
ensureCheckJobs()
|
||||
isLoading.value = false
|
||||
emptyTextVisibility.value = result.isEmpty()
|
||||
|
||||
if (forTags.isEmpty()) {
|
||||
val tagsValues = pullOutTags(result)
|
||||
tags.value = tagsValues
|
||||
tagsListVisibility.value = tagsValues.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,4 +130,21 @@ class MainViewModel(
|
|||
validationManager.ensureScheduledChecks()
|
||||
}
|
||||
}
|
||||
|
||||
private fun pullOutTags(sites: List<Site>): List<String> {
|
||||
return mutableListOf<String>().apply {
|
||||
for (site in sites) {
|
||||
val splitTags = site.tags.toLowerCase()
|
||||
.split(',')
|
||||
splitTags
|
||||
.filter { it.isNotEmpty() }
|
||||
.forEach { tag ->
|
||||
if (!this.contains(tag)) {
|
||||
this.add(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ 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
|
||||
import kotlinx.android.synthetic.main.activity_viewsite.inputTags
|
||||
import kotlinx.android.synthetic.main.activity_viewsite.inputUrl
|
||||
import kotlinx.android.synthetic.main.activity_viewsite.loadingProgress
|
||||
import kotlinx.android.synthetic.main.activity_viewsite.responseTimeoutInput
|
||||
|
@ -95,6 +96,9 @@ class ViewSiteActivity : DarkModeSwitchActivity() {
|
|||
viewModel.onNameError()
|
||||
.toViewError(this, inputName)
|
||||
|
||||
// Tags
|
||||
inputTags.attachLiveData(this, viewModel.tags)
|
||||
|
||||
// Url
|
||||
inputUrl.attachLiveData(this, viewModel.url)
|
||||
viewModel.onUrlError()
|
||||
|
|
|
@ -64,6 +64,7 @@ class ViewSiteViewModel(
|
|||
// Public properties
|
||||
val status = MutableLiveData<Status>()
|
||||
val name = MutableLiveData<String>()
|
||||
val tags = MutableLiveData<String>()
|
||||
val url = MutableLiveData<String>()
|
||||
val timeout = MutableLiveData<Int>()
|
||||
val validationMode = MutableLiveData<ValidationMode>()
|
||||
|
@ -305,6 +306,8 @@ class ViewSiteViewModel(
|
|||
return null
|
||||
}
|
||||
|
||||
val cleanedTags = tags.value?.split(',')?.joinToString { it.trim() } ?: ""
|
||||
|
||||
val newSettings = site.settings!!.copy(
|
||||
validationIntervalMs = getCheckIntervalMs(),
|
||||
validationMode = validationMode.value!!,
|
||||
|
@ -332,6 +335,7 @@ class ViewSiteViewModel(
|
|||
|
||||
return site.copy(
|
||||
name = name.value!!.trim(),
|
||||
tags = cleanedTags,
|
||||
url = url.value!!.trim(),
|
||||
settings = newSettings,
|
||||
retryPolicy = retryPolicy
|
||||
|
|
|
@ -32,6 +32,7 @@ fun ViewSiteViewModel.setModel(site: Site) {
|
|||
|
||||
status.value = site.lastResult?.status ?: WAITING
|
||||
name.value = site.name
|
||||
tags.value = site.tags
|
||||
url.value = site.url
|
||||
timeout.value = settings.networkTimeout
|
||||
|
||||
|
|
5
app/src/main/res/color/unchecked_chip_text.xml
Normal file
5
app/src/main/res/color/unchecked_chip_text.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorAccent" android:state_pressed="false"/>
|
||||
<item android:color="#FFFFFF" android:state_pressed="true"/>
|
||||
</selector>
|
13
app/src/main/res/drawable/checked_chip.xml
Normal file
13
app/src/main/res/drawable/checked_chip.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?colorAccent"/>
|
||||
<stroke
|
||||
android:color="@color/colorAccent_pressed"
|
||||
android:width="1dp"/>
|
||||
<corners android:radius="6dp"/>
|
||||
<padding
|
||||
android:bottom="12dp"
|
||||
android:left="12dp"
|
||||
android:right="12dp"
|
||||
android:top="12dp"/>
|
||||
</shape>
|
13
app/src/main/res/drawable/checked_chip_pressed.xml
Normal file
13
app/src/main/res/drawable/checked_chip_pressed.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/colorAccent_pressed"/>
|
||||
<stroke
|
||||
android:color="?colorAccent"
|
||||
android:width="1dp"/>
|
||||
<corners android:radius="6dp"/>
|
||||
<padding
|
||||
android:bottom="12dp"
|
||||
android:left="12dp"
|
||||
android:right="12dp"
|
||||
android:top="12dp"/>
|
||||
</shape>
|
5
app/src/main/res/drawable/checked_chip_selector.xml
Normal file
5
app/src/main/res/drawable/checked_chip_selector.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/checked_chip" android:state_pressed="false"/>
|
||||
<item android:drawable="@drawable/checked_chip_pressed" android:state_pressed="true"/>
|
||||
</selector>
|
13
app/src/main/res/drawable/unchecked_chip.xml
Normal file
13
app/src/main/res/drawable/unchecked_chip.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?android:windowBackground"/>
|
||||
<stroke
|
||||
android:color="?colorAccent"
|
||||
android:width="1dp"/>
|
||||
<corners android:radius="6dp"/>
|
||||
<padding
|
||||
android:bottom="12dp"
|
||||
android:left="12dp"
|
||||
android:right="12dp"
|
||||
android:top="12dp"/>
|
||||
</shape>
|
13
app/src/main/res/drawable/unchecked_chip_pressed.xml
Normal file
13
app/src/main/res/drawable/unchecked_chip_pressed.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/colorAccent_translucent"/>
|
||||
<stroke
|
||||
android:color="?colorAccent"
|
||||
android:width="1dp"/>
|
||||
<corners android:radius="6dp"/>
|
||||
<padding
|
||||
android:bottom="12dp"
|
||||
android:left="12dp"
|
||||
android:right="12dp"
|
||||
android:top="12dp"/>
|
||||
</shape>
|
5
app/src/main/res/drawable/unchecked_chip_selector.xml
Normal file
5
app/src/main/res/drawable/unchecked_chip_selector.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/unchecked_chip" android:state_pressed="false"/>
|
||||
<item android:drawable="@drawable/unchecked_chip_pressed" android:state_pressed="true"/>
|
||||
</selector>
|
|
@ -49,6 +49,27 @@
|
|||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tagsTiLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="-4dp"
|
||||
android:layout_marginRight="-4dp"
|
||||
android:layout_marginTop="@dimen/content_inset_less"
|
||||
>
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/inputTags"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits=",.?-_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ "
|
||||
android:hint="@string/site_tags_hint"
|
||||
android:inputType="textPersonName|textCapWords|textAutoCorrect"
|
||||
style="@style/NockText.Body"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/urlTiLayout"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -17,6 +17,19 @@
|
|||
|
||||
<include layout="@layout/include_app_bar"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/tags_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="@dimen/content_inset_half"
|
||||
android:paddingEnd="@dimen/content_inset"
|
||||
android:paddingStart="@dimen/content_inset"
|
||||
android:paddingTop="@dimen/content_inset_half"
|
||||
android:scrollbars="none"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -67,6 +67,18 @@
|
|||
style="@style/NockText.Body"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/inputTags"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits=",.?-_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ "
|
||||
android:hint="@string/site_tags_hint"
|
||||
android:inputType="text|textCapWords"
|
||||
android:singleLine="true"
|
||||
tools:ignore="Autofill,UnusedAttribute"
|
||||
style="@style/NockText.Body"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/inputUrl"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -123,9 +135,9 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="-4dp"
|
||||
android:layout_marginStart="-4dp"
|
||||
android:layout_marginTop="@dimen/content_inset_quarter"
|
||||
android:hint="@string/response_timeout_default"
|
||||
android:inputType="number"
|
||||
android:layout_marginTop="@dimen/content_inset_quarter"
|
||||
android:maxLength="8"
|
||||
tools:ignore="Autofill,HardcodedText,LabelFor"
|
||||
style="@style/NockText.Body"
|
||||
|
|
15
app/src/main/res/layout/list_item_tag.xml
Normal file
15
app/src/main/res/layout/list_item_tag.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
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/chip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/content_inset_half"
|
||||
android:background="@drawable/unchecked_chip_selector"
|
||||
android:textColor="?colorAccent"
|
||||
app:textAllCaps="true"
|
||||
tools:text="Testing"
|
||||
style="@style/NockText.Body"
|
||||
/>
|
|
@ -9,5 +9,7 @@
|
|||
|
||||
<color name="lighterGray">#303030</color>
|
||||
<color name="colorAccent">#FF6E40</color>
|
||||
<color name="colorAccent_pressed">#E44615</color>
|
||||
<color name="colorAccent_translucent">#40FF6E40</color>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
<string name="add_site">Add Site</string>
|
||||
<string name="site_name">Site Name</string>
|
||||
<string name="site_url">Site URL</string>
|
||||
<string name="site_tags">Site Tags</string>
|
||||
<string name="site_tags_hint">Site Tags (one, two, three)</string>
|
||||
<string name="please_enter_name">Please enter a name!</string>
|
||||
<string name="please_enter_url">Please enter a URL.</string>
|
||||
<string name="please_enter_valid_url">Please enter a valid URL.</string>
|
||||
|
|
|
@ -32,7 +32,7 @@ import com.afollestad.nocknock.data.model.ValidationResult
|
|||
SiteSettings::class,
|
||||
Site::class
|
||||
],
|
||||
version = 2,
|
||||
version = 3,
|
||||
exportSchema = false
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
@ -52,13 +52,13 @@ abstract class AppDatabase : RoomDatabase() {
|
|||
*
|
||||
* @author Aidan Follestad (@afollestad)
|
||||
*/
|
||||
fun AppDatabase.allSites(forTag: String = ""): List<Site> {
|
||||
val lowercaseTag = forTag.toLowerCase()
|
||||
fun AppDatabase.allSites(tags: List<String> = emptyList()): List<Site> {
|
||||
var all = siteDao().all()
|
||||
if (!forTag.isEmpty()) {
|
||||
all = all.filter {
|
||||
forTag.isEmpty() ||
|
||||
it.tags.toLowerCase().split(",").contains(lowercaseTag)
|
||||
if (tags.isNotEmpty()) {
|
||||
all = all.filter { site ->
|
||||
val itemTags = site.tags.toLowerCase()
|
||||
.split(",")
|
||||
return@filter itemTags.any { tag -> tags.contains(tag) }
|
||||
}
|
||||
}
|
||||
return all.map {
|
||||
|
|
|
@ -27,7 +27,7 @@ class Database1to2Migration : Migration(1, 2) {
|
|||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"CREATE TABLE `retry_policies` (siteId INTEGER PRIMARY KEY NOT NULL, count INTEGER NOT NULL, minutes INTEGER NOT NULL)"
|
||||
"CREATE TABLE `retry_policies` (siteId INTEGER PRIMARY KEY NOT NULL, count INTEGER NOT NULL, minutes INTEGER NOT NULL, lastTryTimestamp INTEGER NOT NULL, triesLeft INTEGER NOT NULL)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -37,9 +37,9 @@ class Database1to2Migration : Migration(1, 2) {
|
|||
*
|
||||
* @author Aidan Follestad (@afollestad)
|
||||
*/
|
||||
class Database2to3Migration : Migration(1, 2) {
|
||||
class Database2to3Migration : Migration(2, 3) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE `sites` ADD COLUMN tags TEXT NOT NULL")
|
||||
database.execSQL("ALTER TABLE `sites` ADD COLUMN tags TEXT NOT NULL DEFAULT ''")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ ext.versions = [
|
|||
// Misc
|
||||
okHttp : '3.12.1',
|
||||
rhino : '1.7.10',
|
||||
chipView : '1.0.3',
|
||||
|
||||
// Kotlin
|
||||
kotlin : '1.3.11',
|
||||
|
|
|
@ -225,6 +225,7 @@ class ValidationJob : JobService() {
|
|||
triesLeft: Int
|
||||
) {
|
||||
retryPolicy.triesLeft = triesLeft
|
||||
retryPolicy.lastTryTimestamp = currentTimeMillis()
|
||||
withContext(IO) {
|
||||
database.retryPolicyDao()
|
||||
.update(retryPolicy)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue