mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-08-04 03:06:41 -04:00
Use SuggestionSearch
instead of Search
for better search functionality.
* Since `Search` is not compatible with those zim files which does not have Xapian index but `SuggestionSearch` have this functionality to search inside those zim files so we have used this. * Update test cases for test new search functionality.
This commit is contained in:
parent
6235ba612a
commit
c4dbe478b8
@ -37,9 +37,8 @@ import org.kiwix.libkiwix.JNIKiwixException
|
|||||||
import org.kiwix.libzim.Archive
|
import org.kiwix.libzim.Archive
|
||||||
import org.kiwix.libzim.DirectAccessInfo
|
import org.kiwix.libzim.DirectAccessInfo
|
||||||
import org.kiwix.libzim.Item
|
import org.kiwix.libzim.Item
|
||||||
import org.kiwix.libzim.Query
|
import org.kiwix.libzim.SuggestionSearch
|
||||||
import org.kiwix.libzim.Search
|
import org.kiwix.libzim.SuggestionSearcher
|
||||||
import org.kiwix.libzim.Searcher
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -56,7 +55,7 @@ class ZimFileReader constructor(
|
|||||||
val zimFile: File,
|
val zimFile: File,
|
||||||
private val jniKiwixReader: Archive,
|
private val jniKiwixReader: Archive,
|
||||||
private val nightModeConfig: NightModeConfig,
|
private val nightModeConfig: NightModeConfig,
|
||||||
private val searcher: Searcher = Searcher(jniKiwixReader)
|
private val searcher: SuggestionSearcher = SuggestionSearcher(jniKiwixReader)
|
||||||
) {
|
) {
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(file: File): ZimFileReader?
|
fun create(file: File): ZimFileReader?
|
||||||
@ -130,9 +129,9 @@ class ZimFileReader constructor(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchSuggestions(prefix: String): Search? =
|
fun searchSuggestions(prefix: String): SuggestionSearch? =
|
||||||
try {
|
try {
|
||||||
searcher.search(Query(prefix))
|
searcher.suggest(prefix)
|
||||||
} catch (ignore: Exception) {
|
} catch (ignore: Exception) {
|
||||||
// to handled the exception if there is no FT Xapian index found in the current zim file
|
// to handled the exception if there is no FT Xapian index found in the current zim file
|
||||||
null
|
null
|
||||||
|
@ -22,14 +22,14 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.yield
|
import kotlinx.coroutines.yield
|
||||||
import org.kiwix.kiwixmobile.core.reader.ZimFileReader
|
import org.kiwix.kiwixmobile.core.reader.ZimFileReader
|
||||||
import org.kiwix.libzim.Search
|
import org.kiwix.libzim.SuggestionSearch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
interface SearchResultGenerator {
|
interface SearchResultGenerator {
|
||||||
suspend fun generateSearchResults(
|
suspend fun generateSearchResults(
|
||||||
searchTerm: String,
|
searchTerm: String,
|
||||||
zimFileReader: ZimFileReader?
|
zimFileReader: ZimFileReader?
|
||||||
): Search?
|
): SuggestionSearch?
|
||||||
}
|
}
|
||||||
|
|
||||||
class ZimSearchResultGenerator @Inject constructor() : SearchResultGenerator {
|
class ZimSearchResultGenerator @Inject constructor() : SearchResultGenerator {
|
||||||
|
@ -30,7 +30,7 @@ data class SearchState(
|
|||||||
if (searchTerm.isEmpty()) {
|
if (searchTerm.isEmpty()) {
|
||||||
recentResults
|
recentResults
|
||||||
} else {
|
} else {
|
||||||
searchResultsWithTerm.search?.let {
|
searchResultsWithTerm.suggestionSearch?.let {
|
||||||
val maximumResults = it.estimatedMatches
|
val maximumResults = it.estimatedMatches
|
||||||
val safeEndIndex =
|
val safeEndIndex =
|
||||||
if (startIndex + 100 < maximumResults) startIndex + 100 else maximumResults
|
if (startIndex + 100 < maximumResults) startIndex + 100 else maximumResults
|
||||||
|
@ -59,7 +59,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchInPreviousScree
|
|||||||
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowDeleteSearchDialog
|
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowDeleteSearchDialog
|
||||||
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowToast
|
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowToast
|
||||||
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.StartSpeechInput
|
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.StartSpeechInput
|
||||||
import org.kiwix.libzim.Search
|
import org.kiwix.libzim.SuggestionSearch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@ -174,4 +174,4 @@ class SearchViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SearchResultsWithTerm(val searchTerm: String, val search: Search?)
|
data class SearchResultsWithTerm(val searchTerm: String, val suggestionSearch: SuggestionSearch?)
|
||||||
|
@ -30,11 +30,11 @@ internal class SearchStateTest {
|
|||||||
@Test
|
@Test
|
||||||
internal fun `visibleResults use searchResults when searchTerm is not empty`() {
|
internal fun `visibleResults use searchResults when searchTerm is not empty`() {
|
||||||
val searchTerm = "notEmpty"
|
val searchTerm = "notEmpty"
|
||||||
val searchWrapper: SearchWrapper = mockk()
|
val suggestionSearchWrapper: SuggestionSearchWrapper = mockk()
|
||||||
val searchIteratorWrapper: SearchIteratorWrapper = mockk()
|
val searchIteratorWrapper: SuggestionIteratorWrapper = mockk()
|
||||||
val entryWrapper: EntryWrapper = mockk()
|
val entryWrapper: SuggestionItemWrapper = mockk()
|
||||||
val estimatedMatches = 1
|
val estimatedMatches = 1
|
||||||
every { searchWrapper.estimatedMatches } returns estimatedMatches.toLong()
|
every { suggestionSearchWrapper.estimatedMatches } returns estimatedMatches.toLong()
|
||||||
// Settings list to hasNext() to ensure it returns true only for the first call.
|
// Settings list to hasNext() to ensure it returns true only for the first call.
|
||||||
// Otherwise, if we do not set this, the method will always return true when checking if the iterator has a next value,
|
// Otherwise, if we do not set this, the method will always return true when checking if the iterator has a next value,
|
||||||
// causing our test case to get stuck in an infinite loop due to this explicit setting.
|
// causing our test case to get stuck in an infinite loop due to this explicit setting.
|
||||||
@ -42,7 +42,7 @@ internal class SearchStateTest {
|
|||||||
every { searchIteratorWrapper.next() } returns entryWrapper
|
every { searchIteratorWrapper.next() } returns entryWrapper
|
||||||
every { entryWrapper.title } returns searchTerm
|
every { entryWrapper.title } returns searchTerm
|
||||||
every {
|
every {
|
||||||
searchWrapper.getResults(
|
suggestionSearchWrapper.getResults(
|
||||||
0,
|
0,
|
||||||
estimatedMatches
|
estimatedMatches
|
||||||
)
|
)
|
||||||
@ -50,7 +50,7 @@ internal class SearchStateTest {
|
|||||||
assertThat(
|
assertThat(
|
||||||
SearchState(
|
SearchState(
|
||||||
searchTerm,
|
searchTerm,
|
||||||
SearchResultsWithTerm("", searchWrapper),
|
SearchResultsWithTerm("", suggestionSearchWrapper),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
FromWebView
|
FromWebView
|
||||||
).getVisibleResults(0)
|
).getVisibleResults(0)
|
||||||
|
@ -74,7 +74,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchInPreviousScree
|
|||||||
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowDeleteSearchDialog
|
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowDeleteSearchDialog
|
||||||
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowToast
|
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowToast
|
||||||
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.StartSpeechInput
|
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.StartSpeechInput
|
||||||
import org.kiwix.libzim.Search
|
import org.kiwix.libzim.SuggestionSearch
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
internal class SearchViewModelTest {
|
internal class SearchViewModelTest {
|
||||||
@ -122,12 +122,12 @@ internal class SearchViewModelTest {
|
|||||||
val item = ZimSearchResultListItem("")
|
val item = ZimSearchResultListItem("")
|
||||||
val searchTerm = "searchTerm"
|
val searchTerm = "searchTerm"
|
||||||
val searchOrigin = FromWebView
|
val searchOrigin = FromWebView
|
||||||
val search: Search = mockk()
|
val suggestionSearch: SuggestionSearch = mockk()
|
||||||
viewModel.state.test(this)
|
viewModel.state.test(this)
|
||||||
.also {
|
.also {
|
||||||
emissionOf(
|
emissionOf(
|
||||||
searchTerm = searchTerm,
|
searchTerm = searchTerm,
|
||||||
search = search,
|
suggestionSearch = suggestionSearch,
|
||||||
databaseResults = listOf(RecentSearchListItem("")),
|
databaseResults = listOf(RecentSearchListItem("")),
|
||||||
searchOrigin = searchOrigin
|
searchOrigin = searchOrigin
|
||||||
)
|
)
|
||||||
@ -135,7 +135,7 @@ internal class SearchViewModelTest {
|
|||||||
.assertValue(
|
.assertValue(
|
||||||
SearchState(
|
SearchState(
|
||||||
searchTerm,
|
searchTerm,
|
||||||
SearchResultsWithTerm(searchTerm, search),
|
SearchResultsWithTerm(searchTerm, suggestionSearch),
|
||||||
listOf(RecentSearchListItem("")),
|
listOf(RecentSearchListItem("")),
|
||||||
searchOrigin
|
searchOrigin
|
||||||
)
|
)
|
||||||
@ -243,14 +243,14 @@ internal class SearchViewModelTest {
|
|||||||
|
|
||||||
private fun TestScope.emissionOf(
|
private fun TestScope.emissionOf(
|
||||||
searchTerm: String,
|
searchTerm: String,
|
||||||
search: Search,
|
suggestionSearch: SuggestionSearch,
|
||||||
databaseResults: List<RecentSearchListItem>,
|
databaseResults: List<RecentSearchListItem>,
|
||||||
searchOrigin: SearchOrigin
|
searchOrigin: SearchOrigin
|
||||||
) {
|
) {
|
||||||
|
|
||||||
coEvery {
|
coEvery {
|
||||||
searchResultGenerator.generateSearchResults(searchTerm, zimFileReader)
|
searchResultGenerator.generateSearchResults(searchTerm, zimFileReader)
|
||||||
} returns search
|
} returns suggestionSearch
|
||||||
viewModel.actions.trySend(Filter(searchTerm)).isSuccess
|
viewModel.actions.trySend(Filter(searchTerm)).isSuccess
|
||||||
recentsFromDb.trySend(databaseResults).isSuccess
|
recentsFromDb.trySend(databaseResults).isSuccess
|
||||||
viewModel.actions.trySend(ScreenWasStartedFrom(searchOrigin)).isSuccess
|
viewModel.actions.trySend(ScreenWasStartedFrom(searchOrigin)).isSuccess
|
||||||
|
@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
package org.kiwix.kiwixmobile.core.search.viewmodel
|
package org.kiwix.kiwixmobile.core.search.viewmodel
|
||||||
|
|
||||||
import org.kiwix.libzim.Entry
|
import org.kiwix.libzim.SuggestionItem
|
||||||
|
|
||||||
internal class EntryWrapper : Entry() {
|
class SuggestionItemWrapper : SuggestionItem() {
|
||||||
override fun getTitle(): String = super.getTitle()
|
override fun getTitle(): String = super.getTitle()
|
||||||
}
|
}
|
@ -18,12 +18,11 @@
|
|||||||
|
|
||||||
package org.kiwix.kiwixmobile.core.search.viewmodel
|
package org.kiwix.kiwixmobile.core.search.viewmodel
|
||||||
|
|
||||||
import org.kiwix.libzim.SearchIterator
|
import org.kiwix.libzim.SuggestionIterator
|
||||||
|
|
||||||
// Create this as a helper class, as we can not directly use libkiwix/libzim functions in testing
|
class SuggestionIteratorWrapper : SuggestionIterator() {
|
||||||
internal class SearchIteratorWrapper : SearchIterator() {
|
|
||||||
override fun remove() {}
|
override fun remove() {}
|
||||||
|
|
||||||
override fun hasNext(): Boolean = super.hasNext()
|
override fun hasNext(): Boolean = super.hasNext()
|
||||||
override fun next(): EntryWrapper = super.next() as EntryWrapper
|
override fun next(): SuggestionItemWrapper = super.next() as SuggestionItemWrapper
|
||||||
}
|
}
|
@ -18,12 +18,11 @@
|
|||||||
|
|
||||||
package org.kiwix.kiwixmobile.core.search.viewmodel
|
package org.kiwix.kiwixmobile.core.search.viewmodel
|
||||||
|
|
||||||
import org.kiwix.libzim.Search
|
import org.kiwix.libzim.SuggestionSearch
|
||||||
|
|
||||||
// Create this as a helper class, as we can not directly use libkiwix/libzim functions in testing
|
class SuggestionSearchWrapper : SuggestionSearch() {
|
||||||
internal class SearchWrapper : Search() {
|
|
||||||
override fun getEstimatedMatches(): Long = super.getEstimatedMatches()
|
override fun getEstimatedMatches(): Long = super.getEstimatedMatches()
|
||||||
|
|
||||||
override fun getResults(start: Int, maxResults: Int): SearchIteratorWrapper =
|
override fun getResults(start: Int, maxResults: Int): SuggestionIteratorWrapper =
|
||||||
super.getResults(start, maxResults) as SearchIteratorWrapper
|
super.getResults(start, maxResults) as SuggestionIteratorWrapper
|
||||||
}
|
}
|
@ -44,11 +44,11 @@ internal class ZimSearchResultGeneratorTest {
|
|||||||
@Test
|
@Test
|
||||||
internal fun `suggestion results are distinct`() {
|
internal fun `suggestion results are distinct`() {
|
||||||
val searchTerm = " "
|
val searchTerm = " "
|
||||||
val searchWrapper: SearchWrapper = mockk()
|
val suggestionSearchWrapper: SuggestionSearchWrapper = mockk()
|
||||||
every { zimFileReader.searchSuggestions(searchTerm) } returns searchWrapper
|
every { zimFileReader.searchSuggestions(searchTerm) } returns suggestionSearchWrapper
|
||||||
runBlocking {
|
runBlocking {
|
||||||
assertThat(zimSearchResultGenerator.generateSearchResults(searchTerm, zimFileReader))
|
assertThat(zimSearchResultGenerator.generateSearchResults(searchTerm, zimFileReader))
|
||||||
.isEqualTo(searchWrapper)
|
.isEqualTo(suggestionSearchWrapper)
|
||||||
verify {
|
verify {
|
||||||
zimFileReader.searchSuggestions(searchTerm)
|
zimFileReader.searchSuggestions(searchTerm)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user