Skip to content

Commit

Permalink
Show bookmarks and highlights in search results
Browse files Browse the repository at this point in the history
  • Loading branch information
xizzhu committed Nov 14, 2020
1 parent 3595579 commit d9088f2
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 31 deletions.
Expand Up @@ -43,7 +43,7 @@ class SearchActivityTest {
.startSearch()
.waitUntilSearchFinished()
.hasSearchResultShown(SearchResult(
"query", MockContents.kjvVerses.subList(0, 5), listOf(),
"query", MockContents.kjvVerses.subList(0, 5), listOf(), listOf(), listOf(),
MockContents.kjvBookNames, MockContents.kjvBookShortNames))
.clickSearchResultItem(1)

Expand Down
Expand Up @@ -19,6 +19,7 @@ package me.xizzhu.android.joshua.search.result
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import me.xizzhu.android.joshua.R
import me.xizzhu.android.joshua.core.Highlight
import me.xizzhu.android.joshua.core.VerseIndex
import me.xizzhu.android.joshua.tests.BaseUnitTest
import me.xizzhu.android.joshua.tests.MockContents
Expand All @@ -31,7 +32,7 @@ import org.junit.runner.RunWith
class SearchVerseItemTest : BaseUnitTest() {
@Test
fun testItemViewType() {
assertEquals(R.layout.item_search_verse, SearchVerseItem(VerseIndex.INVALID, "", "", "", {}).viewType)
assertEquals(R.layout.item_search_verse, SearchVerseItem(VerseIndex.INVALID, "", "", "", Highlight.COLOR_NONE, {}).viewType)
}

@Test
Expand All @@ -40,7 +41,7 @@ class SearchVerseItemTest : BaseUnitTest() {
val bookShortName = MockContents.kjvBookShortNames[0]
val text = MockContents.kjvVerses[0].text.text
val expected = "$bookShortName ${verseIndex.chapterIndex + 1}:${verseIndex.verseIndex + 1}\n$text"
val actual = SearchVerseItem(verseIndex, bookShortName, text, "", {}).textForDisplay.toString()
val actual = SearchVerseItem(verseIndex, bookShortName, text, "", Highlight.COLOR_NONE, {}).textForDisplay.toString()
assertEquals(expected, actual)
}
}
Expand Up @@ -24,11 +24,8 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.scopes.ActivityScoped
import me.xizzhu.android.joshua.core.BibleReadingManager
import me.xizzhu.android.joshua.Navigator
import me.xizzhu.android.joshua.core.Note
import me.xizzhu.android.joshua.core.SettingsManager
import me.xizzhu.android.joshua.core.VerseAnnotationManager
import me.xizzhu.android.joshua.core.*
import me.xizzhu.android.joshua.search.result.SearchResultListPresenter
import me.xizzhu.android.joshua.search.toolbar.SearchToolbarPresenter

Expand All @@ -54,12 +51,14 @@ object SearchModule {
@Provides
fun provideSearchViewModel(searchActivity: SearchActivity,
bibleReadingManager: BibleReadingManager,
bookmarkManager: VerseAnnotationManager<Bookmark>,
highlightManager: VerseAnnotationManager<Highlight>,
noteManager: VerseAnnotationManager<Note>,
settingsManager: SettingsManager): SearchViewModel {
val factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SearchViewModel::class.java)) {
return SearchViewModel(bibleReadingManager, noteManager, settingsManager) as T
return SearchViewModel(bibleReadingManager, bookmarkManager, highlightManager, noteManager, settingsManager) as T
}

throw IllegalArgumentException("Unsupported model class - $modelClass")
Expand Down
Expand Up @@ -24,13 +24,16 @@ import me.xizzhu.android.joshua.utils.firstNotEmpty
data class SearchRequest(val query: String, val instantSearch: Boolean)

data class SearchResult(
val query: String, val verses: List<Verse>, val notes: List<Note>,
val query: String, val verses: List<Verse>,
val bookmarks: List<Pair<Bookmark, Verse>>,
val highlights: List<Pair<Highlight, Verse>>,
val notes: List<Pair<Note, Verse>>,
val bookNames: List<String>, val bookShortNames: List<String>
) {
data class Note(val verseIndex: VerseIndex, val note: String, val verse: String)
}
)

class SearchViewModel(private val bibleReadingManager: BibleReadingManager,
private val bookmarkManager: VerseAnnotationManager<Bookmark>,
private val highlightManager: VerseAnnotationManager<Highlight>,
private val noteManager: VerseAnnotationManager<Note>,
settingsManager: SettingsManager) : BaseSettingsViewModel(settingsManager) {
private val _searchRequest = MutableStateFlow<SearchRequest?>(null)
Expand All @@ -42,17 +45,28 @@ class SearchViewModel(private val bibleReadingManager: BibleReadingManager,

fun search(query: String): Flow<SearchResult> = flow {
val currentTranslation = bibleReadingManager.currentTranslation().firstNotEmpty()

val verses = bibleReadingManager.search(currentTranslation, query)
val versesWithIndexes = verses.associateBy { it.verseIndex }
val bookmarks = bookmarkManager.read(Constants.SORT_BY_BOOK).mapNotNull { bookmark ->
versesWithIndexes[bookmark.verseIndex]?.let { verse -> Pair(bookmark, verse) }
}
val highlights = highlightManager.read(Constants.SORT_BY_BOOK).mapNotNull { highlight ->
versesWithIndexes[highlight.verseIndex]?.let { verse -> Pair(highlight, verse) }
}

val notes = noteManager.search(query).let { notes ->
val verses = bibleReadingManager.readVerses(currentTranslation, notes.map { it.verseIndex })
arrayListOf<SearchResult.Note>().apply {
val versesWithNotes = bibleReadingManager.readVerses(currentTranslation, notes.map { it.verseIndex })
arrayListOf<Pair<Note, Verse>>().apply {
ensureCapacity(notes.size)
notes.forEach { note ->
add(SearchResult.Note(note.verseIndex, note.note, verses[note.verseIndex]?.text?.text ?: ""))
add(Pair(note, versesWithNotes.getValue(note.verseIndex)))
}
}
}

emit(SearchResult(
query, bibleReadingManager.search(currentTranslation, query), notes,
query, verses, bookmarks, highlights, notes,
bibleReadingManager.readBookNames(currentTranslation),
bibleReadingManager.readBookShortNames(currentTranslation)
))
Expand Down
Expand Up @@ -26,6 +26,7 @@ import kotlinx.coroutines.launch
import me.xizzhu.android.joshua.Navigator
import me.xizzhu.android.joshua.R
import me.xizzhu.android.joshua.core.Bible
import me.xizzhu.android.joshua.core.Highlight
import me.xizzhu.android.joshua.core.VerseIndex
import me.xizzhu.android.joshua.infra.activity.BaseSettingsPresenter
import me.xizzhu.android.joshua.infra.arch.*
Expand Down Expand Up @@ -105,7 +106,25 @@ class SearchResultListPresenter(
if (notes.isNotEmpty()) {
items.add(TitleItem(activity.getString(R.string.title_notes), false))
notes.forEach { note ->
items.add(SearchNoteItem(note.verseIndex, bookShortNames[note.verseIndex.bookIndex], note.verse, note.note, query, ::selectVerse))
items.add(SearchNoteItem(
note.first.verseIndex, bookShortNames[note.first.verseIndex.bookIndex], note.second.text.text, note.first.note, query, ::selectVerse
))
}
}

if (bookmarks.isNotEmpty()) {
items.add(TitleItem(activity.getString(R.string.title_bookmarks), false))
bookmarks.forEach { bookmark ->
items.add(SearchVerseItem(bookmark.first.verseIndex, bookShortNames[bookmark.first.verseIndex.bookIndex],
bookmark.second.text.text, query, Highlight.COLOR_NONE, ::selectVerse))
}
}

if (highlights.isNotEmpty()) {
items.add(TitleItem(activity.getString(R.string.title_highlights), false))
highlights.forEach { highlight ->
items.add(SearchVerseItem(highlight.first.verseIndex, bookShortNames[highlight.first.verseIndex.bookIndex],
highlight.second.text.text, query, highlight.first.color, ::selectVerse))
}
}

Expand All @@ -117,7 +136,7 @@ class SearchResultListPresenter(
lastVerseBookIndex = currentVerseBookIndex
}
items.add(SearchVerseItem(verse.verseIndex, bookShortNames[currentVerseBookIndex],
verse.text.text, query, ::selectVerse))
verse.text.text, query, Highlight.COLOR_NONE, ::selectVerse))
}

return items
Expand Down
Expand Up @@ -17,11 +17,16 @@
package me.xizzhu.android.joshua.search.result

import android.annotation.SuppressLint
import android.graphics.Color
import android.text.SpannableStringBuilder
import android.text.style.BackgroundColorSpan
import android.text.style.ForegroundColorSpan
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.ColorInt
import me.xizzhu.android.joshua.R
import me.xizzhu.android.joshua.core.Highlight
import me.xizzhu.android.joshua.core.Settings
import me.xizzhu.android.joshua.core.VerseIndex
import me.xizzhu.android.joshua.ui.*
Expand All @@ -31,6 +36,7 @@ import java.util.*

data class SearchVerseItem(val verseIndex: VerseIndex, private val bookShortName: String,
private val text: String, private val query: String,
@ColorInt private val highlightColor: Int,
val onClicked: (VerseIndex) -> Unit)
: BaseItem(R.layout.item_search_verse, { inflater, parent -> SearchVerseItemViewHolder(inflater, parent) }) {
companion object {
Expand Down Expand Up @@ -70,6 +76,15 @@ data class SearchVerseItem(val verseIndex: VerseIndex, private val bookShortName
}
}

// highlights the verse if needed
if (highlightColor != Highlight.COLOR_NONE) {
SPANNABLE_STRING_BUILDER.setSpan(
BackgroundColorSpan(highlightColor),
ForegroundColorSpan(if (highlightColor == Highlight.COLOR_BLUE) Color.WHITE else Color.BLACK),
SPANNABLE_STRING_BUILDER.length - text.length, SPANNABLE_STRING_BUILDER.length
)
}

return@lazy SPANNABLE_STRING_BUILDER.toCharSequence()
}
}
Expand Down
Expand Up @@ -31,6 +31,12 @@ class SearchViewModelTest : BaseUnitTest() {
@Mock
private lateinit var bibleReadingManager: BibleReadingManager

@Mock
private lateinit var bookmarkManager: VerseAnnotationManager<Bookmark>

@Mock
private lateinit var highlightManager: VerseAnnotationManager<Highlight>

@Mock
private lateinit var noteManager: VerseAnnotationManager<Note>

Expand All @@ -43,7 +49,7 @@ class SearchViewModelTest : BaseUnitTest() {
override fun setup() {
super.setup()

searchViewModel = SearchViewModel(bibleReadingManager, noteManager, settingsManager)
searchViewModel = SearchViewModel(bibleReadingManager, bookmarkManager, highlightManager, noteManager, settingsManager)
}

@Test
Expand All @@ -66,18 +72,26 @@ class SearchViewModelTest : BaseUnitTest() {
val currentTranslation = MockContents.kjvShortName
val query = "query"
`when`(bibleReadingManager.currentTranslation()).thenReturn(flowOf("", currentTranslation))
`when`(bibleReadingManager.search(currentTranslation, query)).thenReturn(listOf(MockContents.kjvVerses[0]))
`when`(bibleReadingManager.search(currentTranslation, query)).thenReturn(listOf(MockContents.kjvVerses[0], MockContents.kjvVerses[1], MockContents.kjvVerses[2], MockContents.kjvVerses[3]))
`when`(bibleReadingManager.readBookNames(currentTranslation)).thenReturn(MockContents.kjvBookNames)
`when`(bibleReadingManager.readBookShortNames(currentTranslation)).thenReturn(MockContents.kjvBookShortNames)
`when`(bibleReadingManager.readVerses(currentTranslation, listOf(VerseIndex(0, 0, 1))))
.thenReturn(mapOf(Pair(VerseIndex(0, 0, 1), MockContents.kjvVerses[1])))
`when`(noteManager.search(query)).thenReturn(listOf(Note(VerseIndex(0, 0, 1), "note", 12345L)))
`when`(bibleReadingManager.readVerses(currentTranslation, listOf(VerseIndex(0, 0, 2))))
.thenReturn(mapOf(Pair(VerseIndex(0, 0, 2), MockContents.kjvVerses[2])))
`when`(bibleReadingManager.readVerses(currentTranslation, listOf(VerseIndex(0, 0, 3))))
.thenReturn(mapOf(Pair(VerseIndex(0, 0, 3), MockContents.kjvVerses[3])))
`when`(bookmarkManager.read(Constants.SORT_BY_BOOK)).thenReturn(listOf(Bookmark(VerseIndex(0, 0, 1), 0L)))
`when`(highlightManager.read(Constants.SORT_BY_BOOK)).thenReturn(listOf(Highlight(VerseIndex(0, 0, 2), Highlight.COLOR_BLUE, 0L)))
`when`(noteManager.search(query)).thenReturn(listOf(Note(VerseIndex(0, 0, 3), "note", 0L)))

assertEquals(
listOf(
SearchResult(
query, listOf(MockContents.kjvVerses[0]),
listOf(SearchResult.Note(VerseIndex(0, 0, 1), "note", MockContents.kjvVerses[1].text.text)),
query, listOf(MockContents.kjvVerses[0], MockContents.kjvVerses[1], MockContents.kjvVerses[2], MockContents.kjvVerses[3]),
listOf(Pair(Bookmark(VerseIndex(0, 0, 1), 0L), MockContents.kjvVerses[1])),
listOf(Pair(Highlight(VerseIndex(0, 0, 2), Highlight.COLOR_BLUE, 0L), MockContents.kjvVerses[2])),
listOf(Pair(Note(VerseIndex(0, 0, 3), "note", 0L), MockContents.kjvVerses[3])),
MockContents.kjvBookNames, MockContents.kjvBookShortNames
)
),
Expand Down
Expand Up @@ -25,8 +25,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import me.xizzhu.android.joshua.Navigator
import me.xizzhu.android.joshua.R
import me.xizzhu.android.joshua.core.Settings
import me.xizzhu.android.joshua.core.VerseIndex
import me.xizzhu.android.joshua.core.*
import me.xizzhu.android.joshua.search.SearchActivity
import me.xizzhu.android.joshua.search.SearchRequest
import me.xizzhu.android.joshua.search.SearchResult
Expand Down Expand Up @@ -69,6 +68,8 @@ class SearchResultListPresenterTest : BaseUnitTest() {
super.setup()

`when`(searchActivity.lifecycle).thenReturn(lifecycle)
`when`(searchActivity.getString(R.string.title_bookmarks)).thenReturn("Bookmarks")
`when`(searchActivity.getString(R.string.title_highlights)).thenReturn("Highlights")
`when`(searchActivity.getString(R.string.title_notes)).thenReturn("Notes")
`when`(searchViewModel.settings()).thenReturn(emptyFlow())
`when`(searchViewModel.searchRequest).thenReturn(emptyFlow())
Expand All @@ -92,7 +93,7 @@ class SearchResultListPresenterTest : BaseUnitTest() {
val query = "query"
`when`(searchViewModel.searchRequest).thenReturn(flowOf(SearchRequest(query, false)))
`when`(searchViewModel.search(query))
.thenReturn(flowOf(SearchResult(query, emptyList(), emptyList(), emptyList(), emptyList())))
.thenReturn(flowOf(SearchResult(query, emptyList(), emptyList(), emptyList(), emptyList(), emptyList(), emptyList())))

searchResultListPresenter.onCreate()

Expand All @@ -114,7 +115,7 @@ class SearchResultListPresenterTest : BaseUnitTest() {
val query = "query"
`when`(searchViewModel.searchRequest).thenReturn(flowOf(SearchRequest(query, true)))
`when`(searchViewModel.search(query))
.thenReturn(flowOf(SearchResult(query, emptyList(), emptyList(), emptyList(), emptyList())))
.thenReturn(flowOf(SearchResult(query, emptyList(), emptyList(), emptyList(), emptyList(), emptyList(), emptyList())))

searchResultListPresenter.onCreate()

Expand Down Expand Up @@ -151,17 +152,29 @@ class SearchResultListPresenterTest : BaseUnitTest() {
val query = "query"
val expected = listOf(
TitleItem("Notes", false),
SearchNoteItem(VerseIndex(0, 0, 1), MockContents.kjvBookShortNames[0], MockContents.kjvVerses[0].text.text, "note", query, searchResultListPresenter::selectVerse),
SearchNoteItem(VerseIndex(0, 0, 3), MockContents.kjvBookShortNames[0], MockContents.kjvVerses[3].text.text, "note", query, searchResultListPresenter::selectVerse),
TitleItem("Bookmarks", false),
SearchVerseItem(
VerseIndex(0, 0, 1), MockContents.kjvBookShortNames[0],
MockContents.kjvVerses[1].text.text, query, Highlight.COLOR_NONE, searchResultListPresenter::selectVerse
),
TitleItem("Highlights", false),
SearchVerseItem(
VerseIndex(0, 0, 2), MockContents.kjvBookShortNames[0],
MockContents.kjvVerses[2].text.text, query, Highlight.COLOR_BLUE, searchResultListPresenter::selectVerse
),
TitleItem(MockContents.kjvBookNames[0], false),
SearchVerseItem(
VerseIndex(0, 0, 0), MockContents.kjvBookShortNames[0],
MockContents.kjvVerses[0].text.text, query, searchResultListPresenter::selectVerse
MockContents.kjvVerses[0].text.text, query, Highlight.COLOR_NONE, searchResultListPresenter::selectVerse
)
)

val searchResult = SearchResult(
query, listOf(MockContents.kjvVerses[0]),
listOf(SearchResult.Note(VerseIndex(0, 0, 1), "note", MockContents.kjvVerses[0].text.text)),
listOf(Pair(Bookmark(VerseIndex(0, 0, 1), 0L), MockContents.kjvVerses[1])),
listOf(Pair(Highlight(VerseIndex(0, 0, 2), Highlight.COLOR_BLUE, 0L), MockContents.kjvVerses[2])),
listOf(Pair(Note(VerseIndex(0, 0, 3), "note", 0L), MockContents.kjvVerses[3])),
MockContents.kjvBookNames, MockContents.kjvBookShortNames
)
val actual = with(searchResultListPresenter) { searchResult.toItems() }
Expand Down
2 changes: 1 addition & 1 deletion docs/CHANGELOG.md
Expand Up @@ -3,7 +3,7 @@ CHANGELOG

#### Next Release
- New features:
- Include notes in search results
- Include bookmarks, highlights, and notes in search results
- Bug fixes:
- Do not crash if it fails to set searchable info
- Catch exception in case error occurred while observing downloaded translations
Expand Down

0 comments on commit d9088f2

Please sign in to comment.