Add a switch between Fuzzy and StrictDomain mode to Autofill search view

This commit is contained in:
Fabian Henneke
2020-04-15 17:22:44 +02:00
committed by Harsh Shandilya
parent 441b4d3b68
commit 7cd6f1d1cf
3 changed files with 80 additions and 60 deletions

View File

@@ -111,78 +111,82 @@ class AutofillFilterView : AppCompatActivity() {
supportActionBar?.hide() supportActionBar?.hide()
bindUI() bindUI()
updateSearch()
setResult(RESULT_CANCELED) setResult(RESULT_CANCELED)
} }
private fun bindUI() { private fun bindUI() {
val recyclerAdapter = SearchableRepositoryAdapter( with(binding) {
R.layout.oreo_autofill_filter_row, rvPassword.apply {
::PasswordViewHolder) { item -> adapter = SearchableRepositoryAdapter(
val file = item.file.relativeTo(item.rootDir) R.layout.oreo_autofill_filter_row,
val pathToIdentifier = directoryStructure.getPathToIdentifierFor(file) ::PasswordViewHolder
val identifier = directoryStructure.getIdentifierFor(file) ) { item ->
val accountPart = directoryStructure.getAccountPartFor(file) val file = item.file.relativeTo(item.rootDir)
check(identifier != null || accountPart != null) { "At least one of identifier and accountPart should always be non-null" } val pathToIdentifier = directoryStructure.getPathToIdentifierFor(file)
title.text = if (identifier != null) { val identifier = directoryStructure.getIdentifierFor(file)
buildSpannedString { val accountPart = directoryStructure.getAccountPartFor(file)
if (pathToIdentifier != null) check(identifier != null || accountPart != null) { "At least one of identifier and accountPart should always be non-null" }
append("$pathToIdentifier/") title.text = if (identifier != null) {
bold { underline { append(identifier) } } buildSpannedString {
if (pathToIdentifier != null)
append("$pathToIdentifier/")
bold { underline { append(identifier) } }
}
} else {
accountPart
}
subtitle.apply {
if (identifier != null && accountPart != null) {
text = accountPart
visibility = View.VISIBLE
} else {
visibility = View.GONE
}
}
}.onItemClicked { _, item ->
decryptAndFill(item)
} }
} else { layoutManager = LinearLayoutManager(context)
accountPart
} }
subtitle.apply { search.apply {
if (identifier != null && accountPart != null) { val initialSearch =
text = accountPart formOrigin.getPrettyIdentifier(applicationContext, untrusted = false)
visibility = View.VISIBLE setText(initialSearch, TextView.BufferType.EDITABLE)
} else { addTextChangedListener { updateSearch() }
visibility = View.GONE }
strictDomainSearch.apply {
visibility = if (formOrigin is FormOrigin.Web) View.VISIBLE else View.GONE
isChecked = formOrigin is FormOrigin.Web
setOnCheckedChangeListener { _, _ -> updateSearch() }
}
shouldMatch.text = getString(
R.string.oreo_autofill_match_with,
formOrigin.getPrettyIdentifier(applicationContext)
)
model.searchResult.observe(this@AutofillFilterView) { result ->
val list = result.passwordItems
(rvPassword.adapter as SearchableRepositoryAdapter).submitList(list) {
rvPassword.scrollToPosition(0)
}
// Switch RecyclerView out for a "no results" message if the new list is empty and
// the message is not yet shown (and vice versa).
if ((list.isEmpty() && rvPasswordSwitcher.nextView.id == rvPasswordEmpty.id) ||
(list.isNotEmpty() && rvPasswordSwitcher.nextView.id == rvPassword.id)
) {
rvPasswordSwitcher.showNext()
} }
} }
}.onItemClicked { _, item ->
decryptAndFill(item)
}
binding.rvPassword.apply {
adapter = recyclerAdapter
layoutManager = LinearLayoutManager(context)
} }
}
val initialFilter = formOrigin.getPrettyIdentifier(applicationContext, untrusted = false) private fun updateSearch() {
binding.search.setText(initialFilter, TextView.BufferType.EDITABLE)
val filterMode =
if (formOrigin is FormOrigin.Web) FilterMode.StrictDomain else FilterMode.Fuzzy
model.search( model.search(
initialFilter, binding.search.text.toString().trim(),
filterMode = filterMode, filterMode = if (binding.strictDomainSearch.isChecked) FilterMode.StrictDomain else FilterMode.Fuzzy,
searchMode = SearchMode.RecursivelyInSubdirectories, searchMode = SearchMode.RecursivelyInSubdirectories,
listMode = ListMode.FilesOnly listMode = ListMode.FilesOnly
) )
binding.search.addTextChangedListener {
model.search(
it.toString().trim(),
filterMode = FilterMode.Fuzzy,
searchMode = SearchMode.RecursivelyInSubdirectories,
listMode = ListMode.FilesOnly
)
}
model.searchResult.observe(this) { result ->
val list = result.passwordItems
recyclerAdapter.submitList(list) {
binding.rvPassword.scrollToPosition(0)
}
// Switch RecyclerView out for a "no results" message if the new list is empty and
// the message is not yet shown (and vice versa).
if ((list.isEmpty() && binding.rvPasswordSwitcher.nextView.id == binding.rvPasswordEmpty.id) ||
(list.isNotEmpty() && binding.rvPasswordSwitcher.nextView.id == binding.rvPassword.id)
)
binding.rvPasswordSwitcher.showNext()
}
binding.shouldMatch.text = getString(
R.string.oreo_autofill_match_with,
formOrigin.getPrettyIdentifier(applicationContext)
)
} }
private fun decryptAndFill(item: PasswordItem) { private fun decryptAndFill(item: PasswordItem) {

View File

@@ -46,7 +46,7 @@
android:id="@+id/rvPasswordSwitcher" android:id="@+id/rvPasswordSwitcher"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_marginTop="@dimen/activity_vertical_margin" android:layout_marginTop="@dimen/activity_vertical_margin"
app:layout_constraintBottom_toTopOf="@id/shouldMatch" app:layout_constraintBottom_toTopOf="@id/strictDomainSearch"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/searchLayout" app:layout_constraintTop_toBottomOf="@id/searchLayout"
@@ -71,6 +71,21 @@
</ViewSwitcher> </ViewSwitcher>
<Switch
android:id="@+id/strictDomainSearch"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:text="@string/oreo_autofill_strict_domain_search"
app:layout_constraintBottom_toTopOf="@id/shouldMatch"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rvPasswordSwitcher"
app:layout_constraintVertical_bias="1.0"
tools:text="Phishing-resistant search"/>
<Switch <Switch
android:id="@+id/shouldMatch" android:id="@+id/shouldMatch"
android:layout_width="0dp" android:layout_width="0dp"
@@ -82,7 +97,7 @@
app:layout_constraintBottom_toTopOf="@id/shouldClear" app:layout_constraintBottom_toTopOf="@id/shouldClear"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rvPasswordSwitcher" app:layout_constraintTop_toBottomOf="@id/strictDomainSearch"
app:layout_constraintVertical_bias="1.0" app:layout_constraintVertical_bias="1.0"
tools:text="Match with example.org" /> tools:text="Match with example.org" />

View File

@@ -250,6 +250,7 @@
<string name="folder_icon_hint">Folder icon</string> <string name="folder_icon_hint">Folder icon</string>
<!-- Oreo Autofill --> <!-- Oreo Autofill -->
<string name="oreo_autofill_strict_domain_search">Phishing-resistant search</string>
<string name="oreo_autofill_match_with">Match with %1$s</string> <string name="oreo_autofill_match_with">Match with %1$s</string>
<string name="oreo_autofill_matches_clear_existing">Clear existing matches</string> <string name="oreo_autofill_matches_clear_existing">Clear existing matches</string>
<string name="oreo_autofill_filter_no_results">No results.</string> <string name="oreo_autofill_filter_no_results">No results.</string>