Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,51 @@ class DeeprIntegratedTest {
.assertIsDisplayed()
}

@Test
fun testMultiKeywordSearchFunctionality() {
// Add links with different content in name and notes
addLinkWithDetails("myapp://test1", "Android Development")
addLinkWithNotesAndDetails("myapp://test2", "Mobile App", "Android programming")
addLinkWithNotesAndDetails("myapp://test3", "iOS Development", "Swift programming")
addLinkWithDetails("myapp://test4", "Web Development")

composeTestRule.waitForIdle()

// Click on the search bar to expand it
composeTestRule
.onNodeWithContentDescription(getString(R.string.search))
.performClick()

composeTestRule.waitForIdle()

// Search with multiple keywords (space as AND operator)
// Should match: "Android Development" and "Mobile App" with "Android programming"
// Should NOT match: "iOS Development" or "Web Development"
composeTestRule
.onNodeWithContentDescription(getString(R.string.search))
.performTextInput("Android Development")

composeTestRule.waitForIdle()

// Verify both matching links are displayed
composeTestRule
.onNodeWithText("Android Development")
.assertIsDisplayed()

composeTestRule
.onNodeWithText("Mobile App")
.assertIsDisplayed()

// Verify non-matching links are not displayed
composeTestRule
.onNodeWithText("iOS Development")
.assertDoesNotExist()

composeTestRule
.onNodeWithText("Web Development")
.assertDoesNotExist()
}

@Test
fun testAddRemoveFavorite() {
// Add a link
Expand Down Expand Up @@ -319,4 +364,42 @@ class DeeprIntegratedTest {

composeTestRule.waitForIdle()
}

// Helper method to add a link with notes and details
private fun addLinkWithNotesAndDetails(
link: String,
name: String,
notes: String,
) {
// Click the FAB
composeTestRule
.onNodeWithContentDescription(getString(R.string.add_link))
.performClick()

composeTestRule.waitForIdle()

// Enter deeplink using placeholder text
composeTestRule
.onNodeWithText("https://example.com or app://deeplink")
.performTextInput(link)

// Enter name using label text
composeTestRule
.onNodeWithText(getString(R.string.enter_link_name))
.performTextInput(name)

// Enter notes using label text
composeTestRule
.onNodeWithText(getString(R.string.enter_notes))
.performScrollTo()
.performTextInput(notes)

// Save
composeTestRule
.onNodeWithText(getString(R.string.save))
.performScrollTo()
.performClick()

composeTestRule.waitForIdle()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
Expand Down Expand Up @@ -78,6 +79,11 @@ class AccountViewModel(
private val analyticsManager: AnalyticsManager,
) : ViewModel(),
KoinComponent {

companion object {
private val SEARCH_TERMS_REGEX = "\\s+".toRegex()
}

private val preferenceDataStore: AppPreferenceDataStore = get()
private val reviewManager: com.yogeshpaliyal.deepr.review.ReviewManager = get()
private val searchQuery = MutableStateFlow("")
Expand Down Expand Up @@ -351,12 +357,19 @@ class AccountViewModel(
if (tags.isEmpty()) "" else tags.joinToString(",") { it.id.toString() }
val tagCount = tags.size.toLong()

// Split search query by spaces to support AND logic
val searchTerms = query.trim().split(SEARCH_TERMS_REGEX).filter { it.isNotEmpty() }

// Use the first term for SQL filtering (to reduce initial result set)
// or empty string if no search terms
val sqlSearchQuery = searchTerms.firstOrNull() ?: ""

linkRepository
.getLinksAndTags(
profileId,
query,
query,
query,
sqlSearchQuery,
sqlSearchQuery,
sqlSearchQuery,
favourite.toLong(),
favourite.toLong(),
tagIdsString,
Expand All @@ -367,6 +380,25 @@ class AccountViewModel(
sortField,
).asFlow()
.mapToList(viewModelScope.coroutineContext)
.map { results ->
// Apply AND logic for multiple search terms
if (searchTerms.size <= 1) {
// If 0 or 1 search term, SQL already filtered correctly
results
} else {
// Convert search terms to lowercase once for performance
val lowerSearchTerms = searchTerms.map { it.lowercase() }

// Filter results to match ALL search terms (AND logic)
results.filter { link ->
lowerSearchTerms.all { lowerTerm ->
(link.link?.lowercase()?.contains(lowerTerm) == true) ||
(link.name?.lowercase()?.contains(lowerTerm) == true) ||
(link.notes?.lowercase()?.contains(lowerTerm) == true)
}
}
}
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)

fun search(query: String) {
Expand Down