Skip to content

Commit

Permalink
Loading resources from workbook (#453)
Browse files Browse the repository at this point in the history
* Test app to load resources with dummy data

* Loading resources from the workbook

* Using WorkbookHeader

* Rendering resource title text content

* Created standalone ResourceListView

* Move files out of testapp folder

* Removed entry point files to be added in new branch

* Remove dead code

* Minor formatting fixes

* Refactored resource group card item constructor

* PR Comments: Internalization & constants

* PR Comment: Kill progressProperty subscription

* PR Comment: Rename title rendering function

* PR Comment: remove unecessary getResources function

* PR Comment: Null-safe resource group loading

* PR Comment: constant val

* PR Comment: finalize ResourceCardItem

* Move mapNotNull to RxUtils, minor code clean
  • Loading branch information
KJoslyn authored and jsarabia committed Jun 3, 2019
1 parent 3c898db commit a61913f
Show file tree
Hide file tree
Showing 13 changed files with 302 additions and 35 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ dependencies {
implementation 'org.wycliffeassociates:8woc2018-common'
implementation 'org.wycliffeassociates:kotlin-resource-container'
implementation 'com.github.WycliffeAssociates:jdenticon-kotlin:-SNAPSHOT'

//Atlassian commonmark (for rendering markdown)
implementation 'com.atlassian.commonmark:commonmark:0.12.1'
}

//tell gradle what to put in the jar
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.wycliffeassociates.otter.jvm.app.ui.resources.view

import org.wycliffeassociates.otter.jvm.app.ui.mainscreen.view.MainScreenStyles
import org.wycliffeassociates.otter.jvm.app.widgets.workbookheader.workbookheader
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.styles.ResourceListStyles
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.view.ResourceListView
import org.wycliffeassociates.otter.jvm.app.ui.resources.viewmodel.ResourcesViewModel
import tornadofx.*

class ResourceListFragment : Fragment() {
val viewModel: ResourcesViewModel by inject()

init {
importStylesheet<MainScreenStyles>()
importStylesheet<ResourceListStyles>()
}
override val root = vbox {

addClass(MainScreenStyles.main)

add(
workbookheader {
labelText = "${viewModel.chapter.title} ${messages["resources"]}"
filterText = messages["hideCompleted"]
}
)
add(
ResourceListView(viewModel.resourceGroups)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.wycliffeassociates.otter.jvm.app.ui.resources.viewmodel

import javafx.application.Platform
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import org.wycliffeassociates.otter.common.data.workbook.*
import org.wycliffeassociates.otter.common.utils.mapNotNull
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.model.ResourceGroupCardItemList
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.model.resourceGroupCardItem
import tornadofx.*

class ResourcesViewModel : ViewModel() {
val activeWorkbookProperty = SimpleObjectProperty<Workbook>()
val workbook: Workbook
get() = activeWorkbookProperty.value

val activeChapterProperty = SimpleObjectProperty<Chapter>()
val chapter: Chapter
get() = activeChapterProperty.value

val activeResourceSlugProperty = SimpleStringProperty()
val resourceSlug: String
get() = activeResourceSlugProperty.value

val resourceGroups: ResourceGroupCardItemList = ResourceGroupCardItemList(mutableListOf())

fun loadResourceGroups() {
chapter
.children
.startWith(chapter)
.mapNotNull { resourceGroupCardItem(it, resourceSlug, onSelect = this::navigateToTakesPage) }
.buffer(2) // Buffering by 2 prevents the list UI from jumping while groups are loading
.subscribe {
Platform.runLater {
resourceGroups.addAll(it)
}
}
}

private fun navigateToTakesPage(resource: Resource) {
// TODO use navigator
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
package org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.model

data class ResourceCardItem(
val title: String
)
import io.reactivex.disposables.CompositeDisposable
import javafx.beans.property.DoubleProperty
import javafx.beans.property.SimpleDoubleProperty
import org.wycliffeassociates.otter.common.data.workbook.AssociatedAudio
import org.wycliffeassociates.otter.common.data.workbook.Resource
import org.commonmark.parser.Parser
import org.commonmark.renderer.text.TextContentRenderer

data class ResourceCardItem(val resource: Resource, val onSelect: () -> Unit) {
val title: String = renderTitleAsPlainText()
private val disposables = CompositeDisposable()
val titleProgressProperty: DoubleProperty = resource.titleAudio.progressProperty()
val bodyProgressProperty: DoubleProperty? = resource.bodyAudio?.progressProperty()
val hasBodyAudio: Boolean = resource.bodyAudio != null

@Suppress("ProtectedInFinal", "Unused")
protected fun finalize() {
clearDisposables()
}

fun clearDisposables() {
disposables.clear()
}

private fun AssociatedAudio.progressProperty(): DoubleProperty {
val progressProperty = SimpleDoubleProperty(0.0)
val sub = this.selected.subscribe {
progressProperty.set( if (it.value != null) 1.0 else 0.0)
}
disposables.add(sub)
return progressProperty
}

companion object {
val parser: Parser = Parser.builder().build()
val renderer: TextContentRenderer = TextContentRenderer.builder().build()
}

private fun renderTitleAsPlainText(): String {
val document = parser.parse(resource.title.text)
return renderer.render(document)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,48 @@
package org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.model

import io.reactivex.Observable
import org.wycliffeassociates.otter.common.data.workbook.*
import tornadofx.*
import tornadofx.FX.Companion.messages

data class ResourceGroupCardItem(
val title: String,
val resources: Observable<ResourceCardItem>
)
) {
fun onRemove() {
resources.forEach {
it.clearDisposables()
}
}
}

fun resourceGroupCardItem(element: BookElement, slug: String, onSelect: (Resource) -> Unit): ResourceGroupCardItem? {
return findResourceGroup(element, slug)?.let { rg ->
ResourceGroupCardItem(
getGroupTitle(element),
getResourceCardItems(rg, onSelect)
)
}
}

private fun findResourceGroup(element: BookElement, slug: String): ResourceGroup? {
return element.resources.firstOrNull {
it.info.slug == slug
}
}

private fun getGroupTitle(element: BookElement): String {
return when (element) {
is Chapter -> "${messages["chapter"]} ${element.title}"
is Chunk -> "${messages["chunk"]} ${element.title}"
else -> element.title
}
}

private fun getResourceCardItems(rg: ResourceGroup, onSelect: (Resource) -> Unit): Observable<ResourceCardItem> {
return rg.resources.map {
ResourceCardItem(it) {
onSelect(it)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.model

import com.sun.javafx.collections.ObservableListWrapper
import javafx.collections.ListChangeListener

class ResourceGroupCardItemList(list: List<ResourceGroupCardItem>) :
ObservableListWrapper<ResourceGroupCardItem>(list) {

init {
addListener(
ListChangeListener<ResourceGroupCardItem> {
while(it.next()) {
if (it.wasRemoved()) {
it.removed.forEach { item ->
item.onRemove()
}
}
}
}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.styles

import javafx.scene.paint.Color
import tornadofx.*

typealias LinearU = Dimension<Dimension.LinearUnits>

class ResourceListStyles : Stylesheet() {

companion object {
val resourceGroupList by cssclass()
}

init {
resourceGroupList {
borderColor += box(Color.TRANSPARENT) // Necessary for border under status bar banner to stay visible
padding = box(0.px, 0.px, 0.px, 80.px) // Left "margin"
scrollBar {
+margin(0.px, 0.px, 0.px, 80.px) // Margin between scrollbar and right side of cards
}

listCell {
// Add space between the cards (top margin)
// But need to make the "margin" at least as large as the dropshadow offsets
+margin(30.px, 4.px, 0.px, 0.px)
}
}
}

private fun margin(top: LinearU, right: LinearU, bottom: LinearU, left: LinearU) = mixin {
padding = box(top, right, bottom, left)
backgroundInsets += box(top, right, bottom, left)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import org.wycliffeassociates.otter.jvm.statusindicator.control.statusindicator
import tornadofx.*
import tornadofx.FX.Companion.messages

class ResourceCard(private val resource: ResourceCardItem) : HBox() {

class ResourceCard(private val item: ResourceCardItem) : HBox() {
val isCurrentResourceProperty = SimpleBooleanProperty(false)
var primaryColorProperty = SimpleObjectProperty<Color>(Color.ORANGE)
var primaryColor: Color by primaryColorProperty
Expand All @@ -33,17 +32,18 @@ class ResourceCard(private val resource: ResourceCardItem) : HBox() {
add(
statusindicator {
initForResourceCard()
progress = 1.0
progressProperty.bind(item.titleProgressProperty)
}
)
add(
statusindicator {
initForResourceCard()
progress = 0.0
item.bodyProgressProperty?.let { progressProperty.bind(it) }
isVisible = item.hasBodyAudio
}
)
}
text(resource.title)
text(item.title)
maxWidth = 150.0
}

Expand All @@ -59,6 +59,9 @@ class ResourceCard(private val resource: ResourceCardItem) : HBox() {
graphic = MaterialIconView(MaterialIcon.APPS, "25px")
maxWidth = 500.0
text = messages["viewRecordings"]
action {
item.onSelect()
}
}
)
}
Expand All @@ -70,7 +73,6 @@ class ResourceCard(private val resource: ResourceCardItem) : HBox() {
trackFill = Color.LIGHTGRAY
indicatorRadius = 3.0
}

}

fun resourcecard(resource: ResourceCardItem, init: ResourceCard.() -> Unit = {}): ResourceCard {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
package org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.view

import javafx.application.Platform
import javafx.scene.layout.VBox
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.model.ResourceGroupCardItem
import tornadofx.*

class ResourceGroupCard(group: ResourceGroupCardItem) : VBox() {
companion object {
const val RENDER_BATCH_SIZE = 10
}
init {
importStylesheet<ResourceGroupCardStyles>()

addClass(ResourceGroupCardStyles.resourceGroupCard)
label(group.title)
group.resources.subscribe {
add(
resourcecard(it)
)

group.resources.buffer(RENDER_BATCH_SIZE).subscribe { items ->
Platform.runLater {
items.forEach {
add(
resourcecard(it)
)
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import javafx.scene.text.FontWeight
import tornadofx.*

class ResourceGroupCardStyles : Stylesheet() {

companion object {
val resourceGroupCard by cssclass()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.view

import javafx.collections.ObservableList
import javafx.scene.control.ListView
import javafx.scene.layout.Priority
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.model.ResourceGroupCardItem
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.styles.ResourceListStyles
import tornadofx.*

class ResourceListView(items: ObservableList<ResourceGroupCardItem>): ListView<ResourceGroupCardItem>(items) {
init {
cellFormat {
graphic = cache(it.title) {
resourcegroupcard(it)
}
}
vgrow = Priority.ALWAYS
isFocusTraversable = false
addClass(ResourceListStyles.resourceGroupList)
}
}
Loading

0 comments on commit a61913f

Please sign in to comment.