fix: Form filling auto search #2204

This commit is contained in:
J-Jamet
2025-10-15 15:14:30 +02:00
parent b3dd3dcfb5
commit 1849dca81d
8 changed files with 104 additions and 78 deletions

View File

@@ -4,7 +4,7 @@ KeePassDX(4.2.0)
* Dialog to manage missing signature #2152 #2155 #2161 #2160 * Dialog to manage missing signature #2152 #2155 #2161 #2160
* Capture error #2159 * Capture error #2159
* Change Passkey Backup Eligibility & Backup State #2135 #2150 #2212 * Change Passkey Backup Eligibility & Backup State #2135 #2150 #2212
* Search settings #2112 #2181 #2187 * Search settings #2112 #2181 #2187 #2204
* Autofill refactoring #765 #2196 * Autofill refactoring #765 #2196
* Small fixes #2157 #2164 #2171 #2122 #2180 #2209 #2214 * Small fixes #2157 #2164 #2171 #2122 #2180 #2209 #2214

View File

@@ -86,6 +86,7 @@ import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseException import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseException
import com.kunzisoft.keepass.database.helper.SearchHelper import com.kunzisoft.keepass.database.helper.SearchHelper
import com.kunzisoft.keepass.database.helper.SearchHelper.getSearchParametersFromSearchInfo
import com.kunzisoft.keepass.database.search.SearchParameters import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.education.GroupActivityEducation import com.kunzisoft.keepass.education.GroupActivityEducation
import com.kunzisoft.keepass.model.DataTime import com.kunzisoft.keepass.model.DataTime
@@ -173,6 +174,7 @@ class GroupActivity : DatabaseLockActivity(),
// Manage group // Manage group
private var mSearchState: SearchState? = null private var mSearchState: SearchState? = null
private var mAutoSearch: Boolean = false // To mainly manage keyboard private var mAutoSearch: Boolean = false // To mainly manage keyboard
private var mTempSearchInfo: Boolean = false // To manage temp search
private var mMainGroupState: GroupState? = null // Group state, not a search private var mMainGroupState: GroupState? = null // Group state, not a search
private var mRootGroup: Group? = null // Root group in the tree private var mRootGroup: Group? = null // Root group in the tree
private var mMainGroup: Group? = null // Main group currently in memory private var mMainGroup: Group? = null // Main group currently in memory
@@ -214,6 +216,7 @@ class GroupActivity : DatabaseLockActivity(),
private val mOnSearchActionExpandListener = object : MenuItem.OnActionExpandListener { private val mOnSearchActionExpandListener = object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(p0: MenuItem): Boolean { override fun onMenuItemActionExpand(p0: MenuItem): Boolean {
searchFiltersView?.visibility = View.VISIBLE searchFiltersView?.visibility = View.VISIBLE
searchFiltersView?.showSearchExpandButton(!mTempSearchInfo)
searchView?.setOnQueryTextListener(mOnSearchQueryTextListener) searchView?.setOnQueryTextListener(mOnSearchQueryTextListener)
searchFiltersView?.onParametersChangeListener = mOnSearchFiltersChangeListener searchFiltersView?.onParametersChangeListener = mOnSearchFiltersChangeListener
@@ -258,6 +261,7 @@ class GroupActivity : DatabaseLockActivity(),
private fun removeSearch() { private fun removeSearch() {
mSearchState = null mSearchState = null
mTempSearchInfo = false
intent.removeExtra(AUTO_SEARCH_KEY) intent.removeExtra(AUTO_SEARCH_KEY)
if (Intent.ACTION_SEARCH == intent.action) { if (Intent.ACTION_SEARCH == intent.action) {
intent.action = Intent.ACTION_DEFAULT intent.action = Intent.ACTION_DEFAULT
@@ -710,37 +714,34 @@ class GroupActivity : DatabaseLockActivity(),
finishNodeAction() finishNodeAction()
} }
/**
* Transform the AUTO_SEARCH_KEY in ACTION_SEARCH, return true if AUTO_SEARCH_KEY was present
*/
private fun transformSearchInfoIntent(intent: Intent) {
// To relaunch the activity as ACTION_SEARCH
val searchInfo: SearchInfo? = intent.retrieveSearchInfo()
val autoSearch = intent.getBooleanExtra(AUTO_SEARCH_KEY, false)
intent.removeExtra(AUTO_SEARCH_KEY)
if (searchInfo != null && autoSearch) {
intent.action = Intent.ACTION_SEARCH
intent.putExtra(SearchManager.QUERY, searchInfo.toString())
}
}
private fun manageIntent(intent: Intent?) { private fun manageIntent(intent: Intent?) {
intent?.let { intent?.let {
if (intent.extras?.containsKey(GROUP_STATE_KEY) == true) { if (intent.extras?.containsKey(GROUP_STATE_KEY) == true) {
mMainGroupState = intent.getParcelableExtraCompat(GROUP_STATE_KEY) mMainGroupState = intent.getParcelableExtraCompat(GROUP_STATE_KEY)
intent.removeExtra(GROUP_STATE_KEY) intent.removeExtra(GROUP_STATE_KEY)
} }
// To transform KEY_SEARCH_INFO in ACTION_SEARCH // To get the form filling search as temp search
transformSearchInfoIntent(intent) val searchInfo: SearchInfo? = intent.retrieveSearchInfo()
val autoSearch = intent.getBooleanExtra(AUTO_SEARCH_KEY, false)
// Get search query // Get search query
if (intent.action == Intent.ACTION_SEARCH) { if (searchInfo != null && autoSearch) {
mAutoSearch = true mAutoSearch = true
val stringQuery = intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: "" mTempSearchInfo = true
intent.action = Intent.ACTION_DEFAULT searchInfo.getSearchParametersFromSearchInfo(this) {
intent.removeExtra(SearchManager.QUERY) mSearchState = SearchState(
mSearchState = SearchState(PreferencesUtil.getDefaultSearchParameters(this).apply { searchParameters = it,
searchQuery = stringQuery firstVisibleItem = mSearchState?.firstVisibleItem ?: 0
}, mSearchState?.firstVisibleItem ?: 0) )
}
} else if (intent.action == Intent.ACTION_SEARCH) {
mAutoSearch = true
mSearchState = SearchState(
searchParameters = PreferencesUtil.getDefaultSearchParameters(this).apply {
searchQuery = intent.getStringExtra(SearchManager.QUERY)
?.trim { it <= ' ' } ?: ""
},
firstVisibleItem = mSearchState?.firstVisibleItem ?: 0
)
} else if (mRequestStartupSearch } else if (mRequestStartupSearch
&& PreferencesUtil.automaticallyFocusSearch(this@GroupActivity)) { && PreferencesUtil.automaticallyFocusSearch(this@GroupActivity)) {
// Expand the search view if defined in settings // Expand the search view if defined in settings
@@ -748,6 +749,8 @@ class GroupActivity : DatabaseLockActivity(),
mRequestStartupSearch = false mRequestStartupSearch = false
addSearch() addSearch()
} }
intent.action = Intent.ACTION_DEFAULT
intent.removeExtra(SearchManager.QUERY)
} }
} }
@@ -772,7 +775,7 @@ class GroupActivity : DatabaseLockActivity(),
// Assign title // Assign title
if (group?.isVirtual == true) { if (group?.isVirtual == true) {
searchFiltersView?.setNumbers(group.numberOfChildEntries) searchFiltersView?.setNumbers(group.numberOfChildEntries)
searchFiltersView?.setCurrentGroupText(mMainGroup?.title ?: "") searchFiltersView?.setCurrentGroupText(mMainGroup?.title ?: getString(R.string.search))
searchFiltersView?.availableOther(mDatabase?.allowEntryCustomFields() ?: false) searchFiltersView?.availableOther(mDatabase?.allowEntryCustomFields() ?: false)
searchFiltersView?.availableApplicationIds(mDatabase?.allowEntryCustomFields() ?: false) searchFiltersView?.availableApplicationIds(mDatabase?.allowEntryCustomFields() ?: false)
searchFiltersView?.availableTags(mDatabase?.allowTags() ?: false) searchFiltersView?.availableTags(mDatabase?.allowTags() ?: false)
@@ -1150,7 +1153,9 @@ class GroupActivity : DatabaseLockActivity(),
finishNodeAction() finishNodeAction()
searchView?.setOnQueryTextListener(null) searchView?.setOnQueryTextListener(null)
searchFiltersView?.saveSearchParameters() if (!mTempSearchInfo) {
searchFiltersView?.saveSearchParameters()
}
} }
private fun addSearchQueryInSearchView(searchQuery: String) { private fun addSearchQueryInSearchView(searchQuery: String) {
@@ -1215,7 +1220,9 @@ class GroupActivity : DatabaseLockActivity(),
if (searchState != null) { if (searchState != null) {
it.expandActionView() it.expandActionView()
addSearchQueryInSearchView(searchState.searchParameters.searchQuery) addSearchQueryInSearchView(searchState.searchParameters.searchQuery)
searchFiltersView?.searchParameters = searchState.searchParameters if (mTempSearchInfo.not()) {
searchFiltersView?.searchParameters = searchState.searchParameters
}
} }
} }
if (it.isActionViewExpanded) { if (it.isActionViewExpanded) {

View File

@@ -53,26 +53,67 @@ object SearchHelper {
private fun getConcreteWebDomain( private fun getConcreteWebDomain(
context: Context, context: Context,
webDomain: String?, webDomain: String?,
concreteWebDomain: (String?) -> Unit concreteWebDomain: (searchSubDomains: Boolean, concreteWebDomain: String?) -> Unit
) { ) {
val domain = webDomain val domain = webDomain
val searchSubDomains = searchSubDomains(context)
if (domain != null) { if (domain != null) {
// Warning, web domain can contains IP, don't crop in this case // Warning, web domain can contains IP, don't crop in this case
if (searchSubDomains(context) if (searchSubDomains
|| Regex(SearchInfo.WEB_IP_REGEX).matches(domain)) { || Regex(SearchInfo.WEB_IP_REGEX).matches(domain)) {
concreteWebDomain.invoke(webDomain) concreteWebDomain.invoke(searchSubDomains, webDomain)
} else { } else {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val publicSuffixList = PublicSuffixList(context) val publicSuffixList = PublicSuffixList(context)
val publicSuffix = publicSuffixList val publicSuffix = publicSuffixList
.getPublicSuffixPlusOne(domain).await() .getPublicSuffixPlusOne(domain).await()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
concreteWebDomain.invoke(publicSuffix) concreteWebDomain.invoke(false, publicSuffix)
} }
} }
} }
} else { } else {
concreteWebDomain.invoke(null) concreteWebDomain.invoke(searchSubDomains, null)
}
}
/**
* Create search parameters asynchronously from [SearchInfo]
*/
fun SearchInfo.getSearchParametersFromSearchInfo(
context: Context,
callback: (SearchParameters) -> Unit
) {
getConcreteWebDomain(
context,
webDomain
) { searchSubDomains, concreteDomain ->
var query = this.toString()
if (isDomainSearch && concreteDomain != null)
query = concreteDomain
callback.invoke(
SearchParameters().apply {
searchQuery = query
allowEmptyQuery = false
searchInTitles = false
searchInUsernames = false
searchInPasswords = false
searchInAppIds = isAppIdSearch
searchInUrls = isDomainSearch
searchByDomain = true
searchBySubDomain = searchSubDomains
searchInRelyingParty = isPasskeySearch
searchInNotes = false
searchInOTP = isOTPSearch
searchInOther = false
searchInUUIDs = false
searchInTags = isTagSearch
searchInCurrentGroup = false
searchInSearchableGroup = true
searchInRecycleBin = false
searchInTemplates = false
}
)
} }
} }
@@ -96,36 +137,10 @@ object SearchHelper {
&& !searchInfo.manualSelection && !searchInfo.manualSelection
&& !searchInfo.containsOnlyNullValues() && !searchInfo.containsOnlyNullValues()
) { ) {
getConcreteWebDomain( searchInfo.getSearchParametersFromSearchInfo(context) { searchParameters ->
context,
searchInfo.webDomain
) { concreteDomain ->
var query = searchInfo.toString()
if (searchInfo.isDomainSearch && concreteDomain != null)
query = concreteDomain
// If search provide results // If search provide results
database.createVirtualGroupFromSearchInfo( database.createVirtualGroupFromSearchInfo(
searchParameters = SearchParameters().apply { searchParameters = searchParameters,
searchQuery = query
allowEmptyQuery = false
searchInTitles = false
searchInUsernames = false
searchInPasswords = false
searchInAppIds = searchInfo.isAppIdSearch
searchInUrls = searchInfo.isDomainSearch
searchByDomain = true
searchBySubDomain = searchSubDomains(context)
searchInRelyingParty = searchInfo.isPasskeySearch
searchInNotes = false
searchInOTP = searchInfo.isOTPSearch
searchInOther = false
searchInUUIDs = false
searchInTags = searchInfo.isTagSearch
searchInCurrentGroup = false
searchInSearchableGroup = true
searchInRecycleBin = false
searchInTemplates = false
},
max = MAX_SEARCH_ENTRY max = MAX_SEARCH_ENTRY
)?.let { searchGroup -> )?.let { searchGroup ->
if (searchGroup.numberOfChildEntries > 0) { if (searchGroup.numberOfChildEntries > 0) {

View File

@@ -210,10 +210,10 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
searchNumbers.text = SearchHelper.showNumberOfSearchResults(numbers) searchNumbers.text = SearchHelper.showNumberOfSearchResults(numbers)
} }
fun setCurrentGroupText(text: String) { fun setCurrentGroupText(text: String?) {
val maxChars = 12 val maxChars = 12
searchCurrentGroup.text = when { searchCurrentGroup.text = when {
text.isEmpty() -> context.getString(R.string.current_group) text.isNullOrEmpty() -> context.getString(R.string.current_group)
text.length > maxChars -> text.substring(0, maxChars) + "" text.length > maxChars -> text.substring(0, maxChars) + ""
else -> text else -> text
} }
@@ -257,6 +257,10 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
) )
} }
fun showSearchExpandButton(show: Boolean) {
searchExpandButton.isVisible = show
}
override fun setVisibility(visibility: Int) { override fun setVisibility(visibility: Int) {
when (visibility) { when (visibility) {
VISIBLE -> { VISIBLE -> {

View File

@@ -37,6 +37,11 @@
android:title="@string/enable_auto_save_database_title" android:title="@string/enable_auto_save_database_title"
android:summary="@string/enable_auto_save_database_summary" android:summary="@string/enable_auto_save_database_summary"
android:defaultValue="@bool/enable_auto_save_database_default"/> android:defaultValue="@bool/enable_auto_save_database_default"/>
<SwitchPreferenceCompat
android:key="@string/auto_focus_search_key"
android:title="@string/auto_focus_search_title"
android:summary="@string/auto_focus_search_summary"
android:defaultValue="@bool/auto_focus_search_default"/>
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="@string/enable_keep_screen_on_key" android:key="@string/enable_keep_screen_on_key"
android:title="@string/enable_keep_screen_on_title" android:title="@string/enable_keep_screen_on_title"
@@ -47,22 +52,6 @@
android:title="@string/enable_screenshot_mode_title" android:title="@string/enable_screenshot_mode_title"
android:summary="@string/enable_screenshot_mode_summary" android:summary="@string/enable_screenshot_mode_summary"
android:defaultValue="@bool/enable_screenshot_mode_key_default"/> android:defaultValue="@bool/enable_screenshot_mode_key_default"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/search">
<SwitchPreferenceCompat
android:key="@string/auto_focus_search_key"
android:title="@string/auto_focus_search_title"
android:summary="@string/auto_focus_search_summary"
android:defaultValue="@bool/auto_focus_search_default"/>
<SwitchPreferenceCompat
android:key="@string/subdomain_search_key"
android:title="@string/subdomain_search_title"
android:summary="@string/subdomain_search_summary"
android:defaultValue="@bool/subdomain_search_default"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory

View File

@@ -19,6 +19,15 @@
--> -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="@string/general">
<SwitchPreferenceCompat
android:key="@string/subdomain_search_key"
android:title="@string/subdomain_search_title"
android:summary="@string/subdomain_search_summary"
android:defaultValue="@bool/subdomain_search_default"/>
</PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:title="@string/keyboard"> android:title="@string/keyboard">
<Preference <Preference

View File

@@ -1,3 +1,4 @@
* Passkeys management #1421 * Passkeys management #1421
* Add KPEX_PASSKEY_FLAG_BE and KPEX_PASSKEY_FLAG_BS flags #2212 * Add KPEX_PASSKEY_FLAG_BE and KPEX_PASSKEY_FLAG_BS flags #2212
* Fix form filling auto search #2204
* Small fixes #2214 * Small fixes #2214

View File

@@ -1,3 +1,4 @@
* Gestion de Passkeys #1421 * Gestion de Passkeys #1421
* Ajout des flags KPEX_PASSKEY_FLAG_BE et KPEX_PASSKEY_FLAG_BS #2212 * Ajout des flags KPEX_PASSKEY_FLAG_BE et KPEX_PASSKEY_FLAG_BS #2212
* Correction de la recherche auto du remplissage de formulaire #2204
* Petites corrections #2214 * Petites corrections #2214