From 1849dca81dc0d0fb1bff813115c091ac9d57630a Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Wed, 15 Oct 2025 15:14:30 +0200 Subject: [PATCH] fix: Form filling auto search #2204 --- CHANGELOG | 2 +- .../keepass/activities/GroupActivity.kt | 59 ++++++++------ .../keepass/database/helper/SearchHelper.kt | 81 +++++++++++-------- .../keepass/view/SearchFiltersView.kt | 8 +- .../main/res/xml/preferences_application.xml | 21 ++--- .../main/res/xml/preferences_form_filling.xml | 9 +++ .../metadata/android/en-US/changelogs/145.txt | 1 + .../metadata/android/fr-FR/changelogs/145.txt | 1 + 8 files changed, 104 insertions(+), 78 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b33a0b1d0..a895281f9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,7 +4,7 @@ KeePassDX(4.2.0) * Dialog to manage missing signature #2152 #2155 #2161 #2160 * Capture error #2159 * Change Passkey Backup Eligibility & Backup State #2135 #2150 #2212 - * Search settings #2112 #2181 #2187 + * Search settings #2112 #2181 #2187 #2204 * Autofill refactoring #765 #2196 * Small fixes #2157 #2164 #2171 #2122 #2180 #2209 #2214 diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt index 195b20509..f0bba2b56 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -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.exception.RegisterInReadOnlyDatabaseException 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.education.GroupActivityEducation import com.kunzisoft.keepass.model.DataTime @@ -173,6 +174,7 @@ class GroupActivity : DatabaseLockActivity(), // Manage group private var mSearchState: SearchState? = null 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 mRootGroup: Group? = null // Root group in the tree private var mMainGroup: Group? = null // Main group currently in memory @@ -214,6 +216,7 @@ class GroupActivity : DatabaseLockActivity(), private val mOnSearchActionExpandListener = object : MenuItem.OnActionExpandListener { override fun onMenuItemActionExpand(p0: MenuItem): Boolean { searchFiltersView?.visibility = View.VISIBLE + searchFiltersView?.showSearchExpandButton(!mTempSearchInfo) searchView?.setOnQueryTextListener(mOnSearchQueryTextListener) searchFiltersView?.onParametersChangeListener = mOnSearchFiltersChangeListener @@ -258,6 +261,7 @@ class GroupActivity : DatabaseLockActivity(), private fun removeSearch() { mSearchState = null + mTempSearchInfo = false intent.removeExtra(AUTO_SEARCH_KEY) if (Intent.ACTION_SEARCH == intent.action) { intent.action = Intent.ACTION_DEFAULT @@ -710,37 +714,34 @@ class GroupActivity : DatabaseLockActivity(), 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?) { intent?.let { if (intent.extras?.containsKey(GROUP_STATE_KEY) == true) { mMainGroupState = intent.getParcelableExtraCompat(GROUP_STATE_KEY) intent.removeExtra(GROUP_STATE_KEY) } - // To transform KEY_SEARCH_INFO in ACTION_SEARCH - transformSearchInfoIntent(intent) + // To get the form filling search as temp search + val searchInfo: SearchInfo? = intent.retrieveSearchInfo() + val autoSearch = intent.getBooleanExtra(AUTO_SEARCH_KEY, false) // Get search query - if (intent.action == Intent.ACTION_SEARCH) { + if (searchInfo != null && autoSearch) { mAutoSearch = true - val stringQuery = intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: "" - intent.action = Intent.ACTION_DEFAULT - intent.removeExtra(SearchManager.QUERY) - mSearchState = SearchState(PreferencesUtil.getDefaultSearchParameters(this).apply { - searchQuery = stringQuery - }, mSearchState?.firstVisibleItem ?: 0) + mTempSearchInfo = true + searchInfo.getSearchParametersFromSearchInfo(this) { + mSearchState = SearchState( + searchParameters = it, + firstVisibleItem = 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 && PreferencesUtil.automaticallyFocusSearch(this@GroupActivity)) { // Expand the search view if defined in settings @@ -748,6 +749,8 @@ class GroupActivity : DatabaseLockActivity(), mRequestStartupSearch = false addSearch() } + intent.action = Intent.ACTION_DEFAULT + intent.removeExtra(SearchManager.QUERY) } } @@ -772,7 +775,7 @@ class GroupActivity : DatabaseLockActivity(), // Assign title if (group?.isVirtual == true) { searchFiltersView?.setNumbers(group.numberOfChildEntries) - searchFiltersView?.setCurrentGroupText(mMainGroup?.title ?: "") + searchFiltersView?.setCurrentGroupText(mMainGroup?.title ?: getString(R.string.search)) searchFiltersView?.availableOther(mDatabase?.allowEntryCustomFields() ?: false) searchFiltersView?.availableApplicationIds(mDatabase?.allowEntryCustomFields() ?: false) searchFiltersView?.availableTags(mDatabase?.allowTags() ?: false) @@ -1150,7 +1153,9 @@ class GroupActivity : DatabaseLockActivity(), finishNodeAction() searchView?.setOnQueryTextListener(null) - searchFiltersView?.saveSearchParameters() + if (!mTempSearchInfo) { + searchFiltersView?.saveSearchParameters() + } } private fun addSearchQueryInSearchView(searchQuery: String) { @@ -1215,7 +1220,9 @@ class GroupActivity : DatabaseLockActivity(), if (searchState != null) { it.expandActionView() addSearchQueryInSearchView(searchState.searchParameters.searchQuery) - searchFiltersView?.searchParameters = searchState.searchParameters + if (mTempSearchInfo.not()) { + searchFiltersView?.searchParameters = searchState.searchParameters + } } } if (it.isActionViewExpanded) { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/helper/SearchHelper.kt b/app/src/main/java/com/kunzisoft/keepass/database/helper/SearchHelper.kt index 3fa040b50..ade5ff023 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/helper/SearchHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/helper/SearchHelper.kt @@ -53,26 +53,67 @@ object SearchHelper { private fun getConcreteWebDomain( context: Context, webDomain: String?, - concreteWebDomain: (String?) -> Unit + concreteWebDomain: (searchSubDomains: Boolean, concreteWebDomain: String?) -> Unit ) { val domain = webDomain + val searchSubDomains = searchSubDomains(context) if (domain != null) { // Warning, web domain can contains IP, don't crop in this case - if (searchSubDomains(context) + if (searchSubDomains || Regex(SearchInfo.WEB_IP_REGEX).matches(domain)) { - concreteWebDomain.invoke(webDomain) + concreteWebDomain.invoke(searchSubDomains, webDomain) } else { CoroutineScope(Dispatchers.IO).launch { val publicSuffixList = PublicSuffixList(context) val publicSuffix = publicSuffixList .getPublicSuffixPlusOne(domain).await() withContext(Dispatchers.Main) { - concreteWebDomain.invoke(publicSuffix) + concreteWebDomain.invoke(false, publicSuffix) } } } } 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.containsOnlyNullValues() ) { - getConcreteWebDomain( - context, - searchInfo.webDomain - ) { concreteDomain -> - var query = searchInfo.toString() - if (searchInfo.isDomainSearch && concreteDomain != null) - query = concreteDomain + searchInfo.getSearchParametersFromSearchInfo(context) { searchParameters -> // If search provide results database.createVirtualGroupFromSearchInfo( - searchParameters = SearchParameters().apply { - 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 - }, + searchParameters = searchParameters, max = MAX_SEARCH_ENTRY )?.let { searchGroup -> if (searchGroup.numberOfChildEntries > 0) { diff --git a/app/src/main/java/com/kunzisoft/keepass/view/SearchFiltersView.kt b/app/src/main/java/com/kunzisoft/keepass/view/SearchFiltersView.kt index fddc63ead..5a501a1e2 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/SearchFiltersView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/SearchFiltersView.kt @@ -210,10 +210,10 @@ class SearchFiltersView @JvmOverloads constructor(context: Context, searchNumbers.text = SearchHelper.showNumberOfSearchResults(numbers) } - fun setCurrentGroupText(text: String) { + fun setCurrentGroupText(text: String?) { val maxChars = 12 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) + "…" else -> text } @@ -257,6 +257,10 @@ class SearchFiltersView @JvmOverloads constructor(context: Context, ) } + fun showSearchExpandButton(show: Boolean) { + searchExpandButton.isVisible = show + } + override fun setVisibility(visibility: Int) { when (visibility) { VISIBLE -> { diff --git a/app/src/main/res/xml/preferences_application.xml b/app/src/main/res/xml/preferences_application.xml index 41b2930b5..47338bff7 100644 --- a/app/src/main/res/xml/preferences_application.xml +++ b/app/src/main/res/xml/preferences_application.xml @@ -37,6 +37,11 @@ android:title="@string/enable_auto_save_database_title" android:summary="@string/enable_auto_save_database_summary" android:defaultValue="@bool/enable_auto_save_database_default"/> + - - - - - - - + + + +