diff --git a/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleMenu.kt b/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleMenu.kt index d64039e..c3c04d2 100644 --- a/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleMenu.kt +++ b/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleMenu.kt @@ -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) { - 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) -: 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) { 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): 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) diff --git a/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleMenuAdapter.kt b/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleMenuAdapter.kt new file mode 100644 index 0000000..14a53a3 --- /dev/null +++ b/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleMenuAdapter.kt @@ -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) +: 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) +} \ No newline at end of file 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 feea95a..18480e8 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 @@ -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() { - - 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 = 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, - 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, 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) - } -} diff --git a/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BookAdapter.kt b/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BookAdapter.kt new file mode 100644 index 0000000..beb5932 --- /dev/null +++ b/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BookAdapter.kt @@ -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() { + + 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 = 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, + 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/PassageView.kt b/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/PassageView.kt new file mode 100644 index 0000000..a7393f4 --- /dev/null +++ b/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/PassageView.kt @@ -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, 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) + } +} \ No newline at end of file