mirror of
https://github.com/MinimalBible/MinimalBible
synced 2024-11-04 23:28:19 -05:00
Display search result items
Still plenty of TODO items, but making great progress Make sure all the tests pass too
This commit is contained in:
parent
71fb362ffe
commit
e552d4d5a6
@ -3,7 +3,7 @@ package org.bspeice.minimalbible.activity.search;
|
|||||||
import android.app.SearchManager;
|
import android.app.SearchManager;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Toast;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
|
||||||
import org.bspeice.minimalbible.Injector;
|
import org.bspeice.minimalbible.Injector;
|
||||||
import org.bspeice.minimalbible.MinimalBible;
|
import org.bspeice.minimalbible.MinimalBible;
|
||||||
@ -16,6 +16,8 @@ import java.util.List;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.InjectView;
|
||||||
import dagger.ObjectGraph;
|
import dagger.ObjectGraph;
|
||||||
|
|
||||||
|
|
||||||
@ -25,6 +27,12 @@ public class BasicSearch extends BaseActivity
|
|||||||
@Inject
|
@Inject
|
||||||
SearchProvider searchProvider;
|
SearchProvider searchProvider;
|
||||||
|
|
||||||
|
@InjectView(R.id.content)
|
||||||
|
SearchResultsListView resultsView;
|
||||||
|
|
||||||
|
@InjectView(R.id.toolbar)
|
||||||
|
Toolbar toolbar;
|
||||||
|
|
||||||
private ObjectGraph searchObjGraph;
|
private ObjectGraph searchObjGraph;
|
||||||
|
|
||||||
private void buildObjGraph() {
|
private void buildObjGraph() {
|
||||||
@ -50,11 +58,18 @@ public class BasicSearch extends BaseActivity
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_search_results);
|
setContentView(R.layout.activity_search_results);
|
||||||
|
|
||||||
inject(this);
|
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());
|
handleSearch(getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Used for launchMode="singleTop"
|
// Used for launchMode="singleTop"
|
||||||
@Override
|
@Override
|
||||||
protected void onNewIntent(Intent intent) {
|
protected void onNewIntent(Intent intent) {
|
||||||
@ -67,8 +82,25 @@ public class BasicSearch extends BaseActivity
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
String query = intent.getStringExtra(SearchManager.QUERY);
|
String query = intent.getStringExtra(SearchManager.QUERY);
|
||||||
Toast.makeText(this, "Searching for: " + query, Toast.LENGTH_SHORT).show();
|
|
||||||
List<Verse> results = searchProvider.basicTextSearch(query);
|
List<Verse> 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<Verse> searchResults) {
|
||||||
|
resultsView.initialize(searchProvider.getBook(), searchResults);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,22 +10,22 @@ import org.crosswire.jsword.index.IndexManager
|
|||||||
* This is the entry point for handling the actual bible search. Likely will support
|
* 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.
|
* 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
|
val defaultSearchType = SearchType.ANY_WORDS
|
||||||
|
|
||||||
[suppress("UNUSED_PARAMETER")]
|
[suppress("UNUSED_PARAMETER")]
|
||||||
public fun basicTextSearch(text: String): List<Verse> {
|
public fun basicTextSearch(text: String): List<Verse> {
|
||||||
if (!isSearchAvailable()) {
|
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()
|
return listOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
val searchText = defaultSearchType decorate text
|
val searchText = defaultSearchType decorate text
|
||||||
// We already checked for null in isSearchAvailable(), but Kotlin
|
// We already checked for null in isSearchAvailable(), but Kotlin
|
||||||
// doesn't keep track of that (yet)
|
// doesn't keep track of that (yet)
|
||||||
val results = b!!.find(searchText)
|
return book!!.find(searchText)
|
||||||
return results map { it as Verse }
|
.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.
|
* This check MUST guarantee that the book is not null.
|
||||||
*/
|
*/
|
||||||
public fun isSearchAvailable(): Boolean =
|
public fun isSearchAvailable(): Boolean =
|
||||||
b != null &&
|
book != null &&
|
||||||
indexManager isIndexed b
|
indexManager isIndexed book
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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<Verse>) {
|
||||||
|
searchResults.setAdapter(SearchResultsAdapter(b, resultsList))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle clicking an item and navigating on the main screen
|
||||||
|
class SearchResultsAdapter(val b: Book, val results: List<Verse>)
|
||||||
|
: RecyclerView.Adapter<ResultViewHolder>() {
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -13,13 +13,13 @@ import org.bspeice.minimalbible.activity.viewer.BookAdapter.ChapterInfo
|
|||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import org.bspeice.minimalbible.service.format.osisparser.OsisParser
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.support.v7.widget.LinearLayoutManager
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import org.bspeice.minimalbible.service.format.osisparser.OsisParser
|
||||||
|
|
||||||
class BibleView(val ctx: Context, val attrs: AttributeSet) : LinearLayout(ctx, attrs) {
|
class BibleView(val ctx: Context, val attrs: AttributeSet) : LinearLayout(ctx, attrs) {
|
||||||
|
|
||||||
@ -145,7 +145,8 @@ class PassageView(val v: TextView, val b: Book)
|
|||||||
|
|
||||||
fun getAllVerses(verses: Progression<Int>, info: ChapterInfo): SpannableStringBuilder {
|
fun getAllVerses(verses: Progression<Int>, info: ChapterInfo): SpannableStringBuilder {
|
||||||
val builder = 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
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,23 +16,41 @@ import org.bspeice.minimalbible.service.format.osisparser.handler.DivineHandler
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse out the OSIS XML into whatever we want!
|
* 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
|
* 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
|
// Don't pass a verse as part of the constructor, but still guarantee
|
||||||
// that it will exist
|
// that it will exist
|
||||||
var verseContent: VerseContent by Delegates.notNull()
|
var verseContent: VerseContent by Delegates.notNull()
|
||||||
|
var builder: SpannableStringBuilder by Delegates.notNull()
|
||||||
|
|
||||||
// TODO: Implement a stack to keep min API 8
|
// TODO: Implement a stack to keep min API 8
|
||||||
val handlerStack = ArrayDeque<TagHandler>()
|
val handlerStack = ArrayDeque<TagHandler>()
|
||||||
|
|
||||||
fun appendVerse(b: Book, v: Verse): VerseContent {
|
fun appendVerse(b: Book, v: Verse,
|
||||||
|
builder: SpannableStringBuilder): VerseContent {
|
||||||
verseContent = VerseContent(v)
|
verseContent = VerseContent(v)
|
||||||
|
this.builder = builder
|
||||||
BookData(b, v).getSAXEventProvider() provideSAXEvents this
|
BookData(b, v).getSAXEventProvider() provideSAXEvents this
|
||||||
return verseContent
|
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,
|
override fun startElement(uri: String, localName: String,
|
||||||
qName: String, attributes: Attributes) {
|
qName: String, attributes: Attributes) {
|
||||||
when (localName) {
|
when (localName) {
|
||||||
|
@ -1,11 +1,26 @@
|
|||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<!-- Set fitsSystemWindows to true since we don't want
|
||||||
|
the search results being overlaid by the nav bar -->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
android:fitsSystemWindows="true"
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
android:orientation="vertical"
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
|
||||||
tools:context="org.bspeice.minimalbible.activity.search.BasicSearch">
|
tools:context="org.bspeice.minimalbible.activity.search.BasicSearch">
|
||||||
|
|
||||||
</RelativeLayout>
|
<android.support.v7.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/colorPrimary"
|
||||||
|
android:minHeight="?attr/actionBarSize" />
|
||||||
|
|
||||||
|
<!-- TODO: Add a divider between results -->
|
||||||
|
<!-- TODO: Add some sort of padding/margin -->
|
||||||
|
<org.bspeice.minimalbible.activity.search.SearchResultsListView
|
||||||
|
android:id="@+id/content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
19
app/src/main/res/layout/view_search_result.xml
Normal file
19
app/src/main/res/layout/view_search_result.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/verseName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/verseContent"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||||
|
</LinearLayout>
|
11
app/src/main/res/layout/view_search_results_list.xml
Normal file
11
app/src/main/res/layout/view_search_results_list.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/search_results"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
Loading…
Reference in New Issue
Block a user