From e552d4d5a6742b5631721d7b171131cc3ebdd563 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Thu, 26 Feb 2015 23:53:20 -0500 Subject: [PATCH] Display search result items Still plenty of TODO items, but making great progress Make sure all the tests pass too --- .../activity/search/BasicSearch.java | 38 ++++++++- .../activity/search/SearchProvider.kt | 12 +-- .../activity/search/SearchResultsView.kt | 77 +++++++++++++++++++ .../minimalbible/activity/viewer/BibleView.kt | 7 +- .../service/format/osisparser/OsisParser.kt | 22 +++++- .../res/layout/activity_search_results.xml | 27 +++++-- .../main/res/layout/view_search_result.xml | 19 +++++ .../res/layout/view_search_results_list.xml | 11 +++ 8 files changed, 193 insertions(+), 20 deletions(-) create mode 100644 app/src/main/kotlin/org/bspeice/minimalbible/activity/search/SearchResultsView.kt create mode 100644 app/src/main/res/layout/view_search_result.xml create mode 100644 app/src/main/res/layout/view_search_results_list.xml diff --git a/app/src/main/java/org/bspeice/minimalbible/activity/search/BasicSearch.java b/app/src/main/java/org/bspeice/minimalbible/activity/search/BasicSearch.java index c9d71fb..8895e86 100644 --- a/app/src/main/java/org/bspeice/minimalbible/activity/search/BasicSearch.java +++ b/app/src/main/java/org/bspeice/minimalbible/activity/search/BasicSearch.java @@ -3,7 +3,7 @@ package org.bspeice.minimalbible.activity.search; import android.app.SearchManager; import android.content.Intent; import android.os.Bundle; -import android.widget.Toast; +import android.support.v7.widget.Toolbar; import org.bspeice.minimalbible.Injector; import org.bspeice.minimalbible.MinimalBible; @@ -16,6 +16,8 @@ import java.util.List; import javax.inject.Inject; +import butterknife.ButterKnife; +import butterknife.InjectView; import dagger.ObjectGraph; @@ -25,6 +27,12 @@ public class BasicSearch extends BaseActivity @Inject SearchProvider searchProvider; + @InjectView(R.id.content) + SearchResultsListView resultsView; + + @InjectView(R.id.toolbar) + Toolbar toolbar; + private ObjectGraph searchObjGraph; private void buildObjGraph() { @@ -50,11 +58,18 @@ public class BasicSearch extends BaseActivity protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_search_results); + inject(this); + ButterKnife.inject(this); + + setSupportActionBar(toolbar); + // We don't set toolbar insets assuming that fitsSystemWindows="true" + // Also don't set "Up" enabled, back is enough. handleSearch(getIntent()); } + // Used for launchMode="singleTop" @Override protected void onNewIntent(Intent intent) { @@ -67,8 +82,25 @@ public class BasicSearch extends BaseActivity return; String query = intent.getStringExtra(SearchManager.QUERY); - Toast.makeText(this, "Searching for: " + query, Toast.LENGTH_SHORT).show(); List results = searchProvider.basicTextSearch(query); - Toast.makeText(this, "Found results: " + results.size(), Toast.LENGTH_SHORT).show(); + + displayTitle(query, results.size()); + displaySearch(results); + } + + public void displayTitle(String query, Integer resultsSize) { + // We can't go through the actual `toolbar` object, we have to + // getSupportActionBar() first. + // http://stackoverflow.com/a/26506858/1454178 + getSupportActionBar().setTitle(buildTitle(query, resultsSize)); + } + + public String buildTitle(String query, Integer resultsSize) { + return "\"" + query + "\" - " + resultsSize + " results"; + } + + // TODO: Inject the book into BasicSearch instead of pulling it out of searchProvider? + public void displaySearch(List searchResults) { + resultsView.initialize(searchProvider.getBook(), searchResults); } } diff --git a/app/src/main/kotlin/org/bspeice/minimalbible/activity/search/SearchProvider.kt b/app/src/main/kotlin/org/bspeice/minimalbible/activity/search/SearchProvider.kt index 8e7df9e..ab5412f 100644 --- a/app/src/main/kotlin/org/bspeice/minimalbible/activity/search/SearchProvider.kt +++ b/app/src/main/kotlin/org/bspeice/minimalbible/activity/search/SearchProvider.kt @@ -10,22 +10,22 @@ import org.crosswire.jsword.index.IndexManager * This is the entry point for handling the actual bible search. Likely will support * an "advanced" search in the future, but for now, basicTextSearch is what you get. */ -class SearchProvider(val indexManager: IndexManager, val b: Book?) { +class SearchProvider(val indexManager: IndexManager, val book: Book?) { val defaultSearchType = SearchType.ANY_WORDS [suppress("UNUSED_PARAMETER")] public fun basicTextSearch(text: String): List { if (!isSearchAvailable()) { - Log.w("SearchProvider", "Search unavailable, index status of ${b?.getInitials()}: ${b?.getIndexStatus()}") + Log.w("SearchProvider", "Search unavailable, index status of ${book?.getInitials()}: ${book?.getIndexStatus()}") return listOf() } val searchText = defaultSearchType decorate text // We already checked for null in isSearchAvailable(), but Kotlin // doesn't keep track of that (yet) - val results = b!!.find(searchText) - return results map { it as Verse } + return book!!.find(searchText) + .map { it as Verse } } /** @@ -34,7 +34,7 @@ class SearchProvider(val indexManager: IndexManager, val b: Book?) { * This check MUST guarantee that the book is not null. */ public fun isSearchAvailable(): Boolean = - b != null && - indexManager isIndexed b + book != null && + indexManager isIndexed book } diff --git a/app/src/main/kotlin/org/bspeice/minimalbible/activity/search/SearchResultsView.kt b/app/src/main/kotlin/org/bspeice/minimalbible/activity/search/SearchResultsView.kt new file mode 100644 index 0000000..310728b --- /dev/null +++ b/app/src/main/kotlin/org/bspeice/minimalbible/activity/search/SearchResultsView.kt @@ -0,0 +1,77 @@ +package org.bspeice.minimalbible.activity.search + +import android.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout +import android.support.v7.widget.RecyclerView +import android.support.v7.widget.LinearLayoutManager +import android.view.LayoutInflater +import org.bspeice.minimalbible.R +import org.crosswire.jsword.passage.Verse +import android.widget.TextView +import org.bspeice.minimalbible.service.format.osisparser.OsisParser +import org.crosswire.jsword.book.Book +import android.view.ViewGroup + +/** + * Created by bspeice on 2/26/15. + */ +class SearchResultsListView(val ctx: Context, val attrs: AttributeSet) : LinearLayout(ctx, attrs) { + + val layoutManager = LinearLayoutManager(ctx) + val inflater = ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater + val contentView = inflater.inflate(R.layout.view_search_results_list, this, true) + val searchResults = contentView.findViewById(R.id.search_results) as RecyclerView; + + { + searchResults setLayoutManager layoutManager + } + + fun initialize(b: Book, resultsList: List) { + searchResults.setAdapter(SearchResultsAdapter(b, resultsList)) + } +} + +// TODO: Handle clicking an item and navigating on the main screen +class SearchResultsAdapter(val b: Book, val results: List) +: RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ResultViewHolder? { + val resultView = SearchResultView(parent) + return ResultViewHolder(resultView) + } + + override fun onBindViewHolder(holder: ResultViewHolder?, position: Int) { + holder?.bind(b, results[position]) + } + + override fun getItemCount(): Int = results.size() +} + +/** + * The ViewHolder object for an individual search result + * TODO: Bold the text found in the query + */ +class ResultViewHolder(val view: SearchResultView) : RecyclerView.ViewHolder(view.contentView) { + + // TODO: Need a nicer way of displaying the book name - currently is ALL CAPS + fun buildVerseName(v: Verse) = "${v.getBook().name()} ${v.getChapter()}:${v.getVerse()}" + + fun buildVerseContent(b: Book, v: Verse, o: OsisParser) = o.parseVerse(b, v) + + fun bind(b: Book, verse: Verse) { + view.verseName setText buildVerseName(verse) + view.verseContent setText buildVerseContent(b, verse, OsisParser()) + } +} + +/** + * A custom view to wrap showing a search result + */ +class SearchResultView(val group: ViewGroup?) { + val inflater = LayoutInflater.from(group?.getContext()) + val contentView = inflater.inflate(R.layout.view_search_result, group, false) + + val verseName = contentView.findViewById(R.id.verseName) as TextView + val verseContent = contentView.findViewById(R.id.verseContent) as TextView +} diff --git a/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleView.kt b/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleView.kt index b0b6dab..a1054f4 100644 --- a/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleView.kt +++ b/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleView.kt @@ -13,13 +13,13 @@ import org.bspeice.minimalbible.activity.viewer.BookAdapter.ChapterInfo import rx.subjects.PublishSubject import android.text.SpannableStringBuilder import android.util.TypedValue -import org.bspeice.minimalbible.service.format.osisparser.OsisParser import android.util.Log import android.content.Context import android.util.AttributeSet import android.support.v7.widget.LinearLayoutManager import android.widget.LinearLayout import android.view.View +import org.bspeice.minimalbible.service.format.osisparser.OsisParser class BibleView(val ctx: Context, val attrs: AttributeSet) : LinearLayout(ctx, attrs) { @@ -120,7 +120,7 @@ class BookAdapter(val b: Book, val prefs: BibleViewerPreferences) override fun getItemCount(): Int = chapterList.size() public fun bindScrollHandler(provider: PublishSubject, - lM: RecyclerView.LayoutManager) { + lM: RecyclerView.LayoutManager) { provider subscribe { val event = it lM scrollToPosition @@ -145,7 +145,8 @@ class PassageView(val v: TextView, val b: Book) fun getAllVerses(verses: Progression, info: ChapterInfo): SpannableStringBuilder { val builder = SpannableStringBuilder() - verses.forEach { OsisParser(builder).appendVerse(b, buildOrdinal(it, info)) } + val parser = OsisParser() + verses.forEach { parser.appendVerse(b, buildOrdinal(it, info), builder) } return builder } diff --git a/app/src/main/kotlin/org/bspeice/minimalbible/service/format/osisparser/OsisParser.kt b/app/src/main/kotlin/org/bspeice/minimalbible/service/format/osisparser/OsisParser.kt index 47841a5..7fd4453 100644 --- a/app/src/main/kotlin/org/bspeice/minimalbible/service/format/osisparser/OsisParser.kt +++ b/app/src/main/kotlin/org/bspeice/minimalbible/service/format/osisparser/OsisParser.kt @@ -16,23 +16,41 @@ import org.bspeice.minimalbible.service.format.osisparser.handler.DivineHandler /** * Parse out the OSIS XML into whatever we want! + * This takes in a SpannableStringBuilder to modify. Normally I'm not a fan + * of mutability, but due to the need for absolute efficiency in this class, + * that's what we're going with. * TODO: Speed up parsing. This is the single most expensive repeated operation */ -class OsisParser(val builder: SpannableStringBuilder) : DefaultHandler() { +class OsisParser() : DefaultHandler() { // Don't pass a verse as part of the constructor, but still guarantee // that it will exist var verseContent: VerseContent by Delegates.notNull() + var builder: SpannableStringBuilder by Delegates.notNull() // TODO: Implement a stack to keep min API 8 val handlerStack = ArrayDeque() - fun appendVerse(b: Book, v: Verse): VerseContent { + fun appendVerse(b: Book, v: Verse, + builder: SpannableStringBuilder): VerseContent { verseContent = VerseContent(v) + this.builder = builder BookData(b, v).getSAXEventProvider() provideSAXEvents this return verseContent } + /** + * Parse a verse and return its content + * Only good for parsing a single verse at a time, + * but gives a cleaner API to work with (and means that + * we can just use the default constructor) + */ + fun parseVerse(b: Book, v: Verse): SpannableStringBuilder { + val mBuilder = SpannableStringBuilder() + appendVerse(b, v, mBuilder) + return mBuilder + } + override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) { when (localName) { diff --git a/app/src/main/res/layout/activity_search_results.xml b/app/src/main/res/layout/activity_search_results.xml index b8bd3eb..56b10fe 100644 --- a/app/src/main/res/layout/activity_search_results.xml +++ b/app/src/main/res/layout/activity_search_results.xml @@ -1,11 +1,26 @@ - + - + + + + + + + + diff --git a/app/src/main/res/layout/view_search_result.xml b/app/src/main/res/layout/view_search_result.xml new file mode 100644 index 0000000..9bd9950 --- /dev/null +++ b/app/src/main/res/layout/view_search_result.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_search_results_list.xml b/app/src/main/res/layout/view_search_results_list.xml new file mode 100644 index 0000000..183d486 --- /dev/null +++ b/app/src/main/res/layout/view_search_results_list.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file