Some migration refactoring

These classes were getting too big
parser-fixes
Bradlee Speice 2015-04-02 19:20:40 -04:00
parent 2af409b3b0
commit 8533cf523c
5 changed files with 252 additions and 230 deletions

View File

@ -2,21 +2,15 @@ package org.bspeice.minimalbible.activity.viewer
import android.content.Context
import android.content.res.Resources
import android.support.annotation.LayoutRes
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseExpandableListAdapter
import android.widget.ExpandableListView
import android.widget.LinearLayout
import android.widget.TextView
import org.bspeice.minimalbible.R
import org.crosswire.jsword.book.Book
import org.crosswire.jsword.book.bookName
import org.crosswire.jsword.book.getVersification
import org.crosswire.jsword.versification.BibleBook
import org.crosswire.jsword.versification.getBooks
import rx.subjects.PublishSubject
class BibleMenu(val ctx: Context, val attrs: AttributeSet) : LinearLayout(ctx, attrs) {
@ -25,7 +19,7 @@ class BibleMenu(val ctx: Context, val attrs: AttributeSet) : LinearLayout(ctx, a
val menuContent = contentView.findViewById(R.id._bible_menu) as ExpandableListView
fun doInitialize(b: Book, publisher: PublishSubject<BookScrollEvent>) {
val adapter = BibleAdapter(b, publisher)
val adapter = BibleMenuAdapter(b, publisher)
menuContent setAdapter adapter
publisher subscribe {
menuContent.collapseGroup(adapter.getGroupIdForBook(it.b))
@ -33,108 +27,15 @@ class BibleMenu(val ctx: Context, val attrs: AttributeSet) : LinearLayout(ctx, a
}
}
/**
* The actual adapter for displaying a book's menu navigation system.
* There are a couple of notes about this:
* Books are displayed with one row per BibleBook (Genesis, Exodus, etc.) as the group.
* Within each group, there are 3 chapters listed per row (to save space). In order to
* accommodate this, some slightly funky mathematics have to be used, and this is documented.
* Additionally, it doesn't make a whole lot of sense to genericize this using constants
* unless we go to programmatic layouts, since we still need to know the view ID's ahead of time.
*
* TODO: Refactor this so the math parts are separate from the actual override functions,
* so it's easier to test.
*/
class BibleAdapter(val b: Book, val scrollPublisher: PublishSubject<BookScrollEvent>)
: BaseExpandableListAdapter() {
// Map BibleBooks to the number of chapters they have
val menuMappings = b.getVersification().getBooks().map {
Pair(it, b.getVersification().getLastChapter(it))
}
fun getGroupIdForBook(b: BibleBook) = menuMappings.indexOf(
menuMappings.first { it.first == b }
)
var groupHighlighted: Int = 0
override fun getGroupCount(): Int = menuMappings.count()
fun getChaptersForGroup(group: Int) = menuMappings[group].second
/**
* Get the number of child views for a given book.
* What makes this complicated is that we display 3 chapters per row.
* To make sure we include everything and account for integer division,
* we have to add a row if the chapter count modulo 3 is not even.
*/
override fun getChildrenCount(group: Int): Int {
val chapterCount = getChaptersForGroup(group)
return when (chapterCount % 3) {
0 -> chapterCount / 3
else -> (chapterCount / 3) + 1
}
}
override fun getGroup(group: Int): String = b.bookName(menuMappings[group].first)
/**
* Get the starting chapter number for this child view
* In order to account for displaying 3 chapters per line,
* we need to multiply by three, and then add 1 for the index offset
*/
override fun getChild(group: Int, child: Int): Int = (child * 3) + 1
override fun getGroupId(group: Int): Long = group.toLong()
override fun getChildId(group: Int, child: Int): Long = child.toLong()
override fun hasStableIds(): Boolean = true
override fun isChildSelectable(group: Int, child: Int): Boolean = true
override fun getGroupView(position: Int, expanded: Boolean,
convertView: View?, parent: ViewGroup): View =
GroupItemHolder.init(
getOrInflate(convertView, parent, R.layout.list_bible_menu_group),
getGroup(position),
position == groupHighlighted)
override fun getChildView(group: Int, child: Int, isLast: Boolean,
convertView: View?, parent: ViewGroup): View {
val chapterStart = getChild(group, child)
val chapterCount = getChaptersForGroup(group)
val chapterEnd =
if (chapterCount < chapterStart + 2)
chapterCount
else
chapterStart + 2
val view = ChildItemHolder.init(
getOrInflate(convertView, parent, R.layout.list_bible_menu_child),
chapterStart..chapterEnd,
menuMappings[group].first,
scrollPublisher
)
return view
}
private fun getOrInflate(v: View?, p: ViewGroup, LayoutRes layout: Int) =
v ?: (p.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)
.inflate(layout, p, false)
}
class GroupItemHolder(val bindTo: View) {
class BibleMenuGroup(val bindTo: View) {
val content = bindTo.findViewById(R.id.content) as TextView
val resources = bindTo.getResources(): Resources
companion object {
fun init(v: View, obj: Any, highlighted: Boolean): View {
val holder =
if (v.getTag() != null) v.getTag() as GroupItemHolder
else GroupItemHolder(v)
if (v.getTag() != null) v.getTag() as BibleMenuGroup
else BibleMenuGroup(v)
holder.bind(obj, highlighted)
return v
@ -155,7 +56,7 @@ class GroupItemHolder(val bindTo: View) {
* Bind the child items. There are some funky math things going on since
* we display three chapters per row, check the adapter for more documentation
*/
class ChildItemHolder(val bindTo: View, val book: BibleBook,
class BibleMenuChild(val bindTo: View, val book: BibleBook,
val scrollPublisher: PublishSubject<BookScrollEvent>) {
val content1 = bindTo.findViewById(R.id.content1) as TextView
val content2 = bindTo.findViewById(R.id.content2) as TextView
@ -165,8 +66,8 @@ class ChildItemHolder(val bindTo: View, val book: BibleBook,
fun init(v: View, obj: IntRange, book: BibleBook,
scrollPublisher: PublishSubject<BookScrollEvent>): View {
val holder =
if (v.getTag() != null) v.getTag() as ChildItemHolder
else ChildItemHolder(v, book, scrollPublisher)
if (v.getTag() != null) v.getTag() as BibleMenuChild
else BibleMenuChild(v, book, scrollPublisher)
holder.clearViews()
holder.bind(obj)

View File

@ -0,0 +1,108 @@
package org.bspeice.minimalbible.activity.viewer
import android.content.Context
import android.support.annotation.LayoutRes
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseExpandableListAdapter
import org.bspeice.minimalbible.R
import org.crosswire.jsword.book.Book
import org.crosswire.jsword.book.bookName
import org.crosswire.jsword.book.getVersification
import org.crosswire.jsword.versification.BibleBook
import org.crosswire.jsword.versification.getBooks
import rx.subjects.PublishSubject
/**
* The actual adapter for displaying a book's menu navigation system.
* There are a couple of notes about this:
* Books are displayed with one row per BibleBook (Genesis, Exodus, etc.) as the group.
* Within each group, there are 3 chapters listed per row (to save space). In order to
* accommodate this, some slightly funky mathematics have to be used, and this is documented.
* Additionally, it doesn't make a whole lot of sense to genericize this using constants
* unless we go to programmatic layouts, since we still need to know the view ID's ahead of time.
*
* TODO: Refactor this so the math parts are separate from the actual override functions,
* so it's easier to test.
*/
class BibleMenuAdapter(val b: Book, val scrollPublisher: PublishSubject<BookScrollEvent>)
: BaseExpandableListAdapter() {
// Map BibleBooks to the number of chapters they have
val menuMappings = b.getVersification().getBooks().map {
Pair(it, b.getVersification().getLastChapter(it))
}
fun getGroupIdForBook(b: BibleBook) = menuMappings.indexOf(
menuMappings.first { it.first == b }
)
var groupHighlighted: Int = 0
override fun getGroupCount(): Int = menuMappings.count()
fun getChaptersForGroup(group: Int) = menuMappings[group].second
/**
* Get the number of child views for a given book.
* What makes this complicated is that we display 3 chapters per row.
* To make sure we include everything and account for integer division,
* we have to add a row if the chapter count modulo 3 is not even.
*/
override fun getChildrenCount(group: Int): Int {
val chapterCount = getChaptersForGroup(group)
return when (chapterCount % 3) {
0 -> chapterCount / 3
else -> (chapterCount / 3) + 1
}
}
override fun getGroup(group: Int): String = b.bookName(menuMappings[group].first)
/**
* Get the starting chapter number for this child view
* In order to account for displaying 3 chapters per line,
* we need to multiply by three, and then add 1 for the index offset
*/
override fun getChild(group: Int, child: Int): Int = (child * 3) + 1
override fun getGroupId(group: Int): Long = group.toLong()
override fun getChildId(group: Int, child: Int): Long = child.toLong()
override fun hasStableIds(): Boolean = true
override fun isChildSelectable(group: Int, child: Int): Boolean = true
override fun getGroupView(position: Int, expanded: Boolean,
convertView: View?, parent: ViewGroup): View =
BibleMenuGroup.init(
getOrInflate(convertView, parent, R.layout.list_bible_menu_group),
getGroup(position),
position == groupHighlighted)
override fun getChildView(group: Int, child: Int, isLast: Boolean,
convertView: View?, parent: ViewGroup): View {
val chapterStart = getChild(group, child)
val chapterCount = getChaptersForGroup(group)
val chapterEnd =
if (chapterCount < chapterStart + 2)
chapterCount
else
chapterStart + 2
val view = BibleMenuChild.init(
getOrInflate(convertView, parent, R.layout.list_bible_menu_child),
chapterStart..chapterEnd,
menuMappings[group].first,
scrollPublisher
)
return view
}
private fun getOrInflate(v: View?, p: ViewGroup, LayoutRes layout: Int) =
v ?: (p.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)
.inflate(layout, p, false)
}

View File

@ -3,22 +3,12 @@ package org.bspeice.minimalbible.activity.viewer
import android.content.Context
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.text.SpannableStringBuilder
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import org.bspeice.minimalbible.R
import org.bspeice.minimalbible.activity.viewer.BookAdapter.ChapterInfo
import org.bspeice.minimalbible.service.format.osisparser.OsisParser
import org.crosswire.jsword.book.Book
import org.crosswire.jsword.book.getVersification
import org.crosswire.jsword.versification.BibleBook
import org.crosswire.jsword.versification.getBooks
import rx.subjects.PublishSubject
class BibleView(val ctx: Context, val attrs: AttributeSet) : LinearLayout(ctx, attrs) {
@ -41,117 +31,3 @@ class BibleView(val ctx: Context, val attrs: AttributeSet) : LinearLayout(ctx, a
}
}
/**
* Adapter used for displaying a book
* Displays one chapter at a time,
* as each TextView widget is it's own line break
*/
class BookAdapter(val b: Book, val prefs: BibleViewerPreferences)
: RecyclerView.Adapter<PassageView>() {
val versification = b.getVersification()
val bookList = versification.getBooks()
// val chapterCount = bookList.map { versification.getLastChapter(it) - 1 }.sum()
/**
* Store information needed to decode the text of a chapter
* Book, chapter, and bibleBook should be pretty self-explanatory
* The vStart, vEnd, and vOffset are needed to map between verses relative to their chapter,
* and the actual verse ordinal needed for parsing the text.
* So Genesis 1:1 would be chapter 1, bibleBook Genesis, vStart 1, vOffset 2
* since it actually starts at ordinal 3
*/
data class ChapterInfo(val book: Book, val chapter: Int, val bibleBook: BibleBook,
val vStart: Int, val vEnd: Int, val vOffset: Int)
/**
* A list of all ChapterInfo objects needed for displaying a book
* The for expression probably looks a bit nicer:
* for {
* book <- bookList
* chapter <- 1..versification.getLastChapter(currentBook)
* } yield ChapterInfo(...)
*
* Also note that getLastVerse() returns the number of verses in a chapter,
* not the actual last verse's ordinal
*/
// TODO: Lazy compute values needed for this list
val chapterList: List<ChapterInfo> = bookList.flatMap {
val currentBook = it
(1..versification.getLastChapter(currentBook)).map {
val firstVerseOrdinal = versification.getFirstVerse(currentBook, it)
val verseOrdinalOffset = firstVerseOrdinal - 1
val verseCount = versification.getLastVerse(currentBook, it)
val firstVerseRelative = 1
val lastVerseRelative = firstVerseRelative + verseCount
ChapterInfo(b, it, currentBook,
firstVerseRelative, lastVerseRelative, verseOrdinalOffset)
}
}
/**
* I'm not sure what the position argument actually represents,
* but on initial load it doesn't change
*/
override fun onCreateViewHolder(parent: ViewGroup?,
position: Int): PassageView {
val emptyView = LayoutInflater.from(parent!!.getContext())
.inflate(R.layout.viewer_passage_view, parent, false) as TextView
// TODO: Listen for changes to the text size?
emptyView.setTextSize(TypedValue.COMPLEX_UNIT_SP, prefs.baseTextSize().toFloat())
val passage = PassageView(emptyView, b)
return passage
}
/**
* Bind an existing view to its chapter content
*/
override fun onBindViewHolder(view: PassageView, position: Int) {
prefs.currentChapter(position)
return view bind chapterList[position]
}
/**
* Get the number of chapters in the book
*/
override fun getItemCount(): Int = chapterList.size()
public fun bindScrollHandler(provider: PublishSubject<BookScrollEvent>,
lM: RecyclerView.LayoutManager) {
provider subscribe {
val event = it
lM scrollToPosition
// Get all objects in the form (index, object)
chapterList.withIndex()
// get one that matches our book and chapter
.first {
event.b == it.value.bibleBook &&
event.chapter == it.value.chapter
}
// and get that index value to scroll to
.index
}
}
}
class PassageView(val v: TextView, val b: Book)
: RecyclerView.ViewHolder(v) {
fun buildOrdinal(verse: Int, info: ChapterInfo) =
b.getVersification().decodeOrdinal(verse + info.vOffset)
fun getAllVerses(verses: Progression<Int>, info: ChapterInfo): SpannableStringBuilder {
val builder = SpannableStringBuilder()
val parser = OsisParser()
verses.forEach { parser.appendVerse(b, buildOrdinal(it, info), builder) }
return builder
}
fun bind(info: ChapterInfo) {
Log.d("PassageView", "Binding chapter ${info.chapter}")
v setText getAllVerses(info.vStart..info.vEnd, info)
}
}

View File

@ -0,0 +1,109 @@
package org.bspeice.minimalbible.activity.viewer
import android.support.v7.widget.RecyclerView
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import org.bspeice.minimalbible.R
import org.crosswire.jsword.book.Book
import org.crosswire.jsword.book.getVersification
import org.crosswire.jsword.versification.BibleBook
import org.crosswire.jsword.versification.getBooks
import rx.subjects.PublishSubject
/**
* Adapter used for displaying a book
* Displays one chapter at a time,
* as each TextView widget is it's own line break
*/
class BookAdapter(val b: Book, val prefs: BibleViewerPreferences)
: RecyclerView.Adapter<PassageView>() {
val versification = b.getVersification()
val bookList = versification.getBooks()
// val chapterCount = bookList.map { versification.getLastChapter(it) - 1 }.sum()
/**
* Store information needed to decode the text of a chapter
* Book, chapter, and bibleBook should be pretty self-explanatory
* The vStart, vEnd, and vOffset are needed to map between verses relative to their chapter,
* and the actual verse ordinal needed for parsing the text.
* So Genesis 1:1 would be chapter 1, bibleBook Genesis, vStart 1, vOffset 2
* since it actually starts at ordinal 3
*/
data class ChapterInfo(val book: Book, val chapter: Int, val bibleBook: BibleBook,
val vStart: Int, val vEnd: Int, val vOffset: Int)
/**
* A list of all ChapterInfo objects needed for displaying a book
* The for expression probably looks a bit nicer:
* for {
* book <- bookList
* chapter <- 1..versification.getLastChapter(currentBook)
* } yield ChapterInfo(...)
*
* Also note that getLastVerse() returns the number of verses in a chapter,
* not the actual last verse's ordinal
*/
// TODO: Lazy compute values needed for this list
val chapterList: List<ChapterInfo> = bookList.flatMap {
val currentBook = it
(1..versification.getLastChapter(currentBook)).map {
val firstVerseOrdinal = versification.getFirstVerse(currentBook, it)
val verseOrdinalOffset = firstVerseOrdinal - 1
val verseCount = versification.getLastVerse(currentBook, it)
val firstVerseRelative = 1
val lastVerseRelative = firstVerseRelative + verseCount
ChapterInfo(b, it, currentBook,
firstVerseRelative, lastVerseRelative, verseOrdinalOffset)
}
}
/**
* I'm not sure what the position argument actually represents,
* but on initial load it doesn't change
*/
override fun onCreateViewHolder(parent: ViewGroup?,
position: Int): PassageView {
val emptyView = LayoutInflater.from(parent!!.getContext())
.inflate(R.layout.viewer_passage_view, parent, false) as TextView
// TODO: Listen for changes to the text size?
emptyView.setTextSize(TypedValue.COMPLEX_UNIT_SP, prefs.baseTextSize().toFloat())
val passage = PassageView(emptyView, b)
return passage
}
/**
* Bind an existing view to its chapter content
*/
override fun onBindViewHolder(view: PassageView, position: Int) {
prefs.currentChapter(position)
return view bind chapterList[position]
}
/**
* Get the number of chapters in the book
*/
override fun getItemCount(): Int = chapterList.size()
public fun bindScrollHandler(provider: PublishSubject<BookScrollEvent>,
lM: RecyclerView.LayoutManager) {
provider subscribe {
val event = it
lM scrollToPosition
// Get all objects in the form (index, object)
chapterList.withIndex()
// get one that matches our book and chapter
.first {
event.b == it.value.bibleBook &&
event.chapter == it.value.chapter
}
// and get that index value to scroll to
.index
}
}
}

View File

@ -0,0 +1,28 @@
package org.bspeice.minimalbible.activity.viewer
import android.support.v7.widget.RecyclerView
import android.text.SpannableStringBuilder
import android.util.Log
import android.widget.TextView
import org.bspeice.minimalbible.service.format.osisparser.OsisParser
import org.crosswire.jsword.book.Book
import org.crosswire.jsword.book.getVersification
class PassageView(val v: TextView, val b: Book)
: RecyclerView.ViewHolder(v) {
fun buildOrdinal(verse: Int, info: BookAdapter.ChapterInfo) =
b.getVersification().decodeOrdinal(verse + info.vOffset)
fun getAllVerses(verses: Progression<Int>, info: BookAdapter.ChapterInfo): SpannableStringBuilder {
val builder = SpannableStringBuilder()
val parser = OsisParser()
verses.forEach { parser.appendVerse(b, buildOrdinal(it, info), builder) }
return builder
}
fun bind(info: BookAdapter.ChapterInfo) {
Log.d("PassageView", "Binding chapter ${info.chapter}")
v setText getAllVerses(info.vStart..info.vEnd, info)
}
}