Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kj recordable view refactor #471

Merged
merged 31 commits into from
Aug 12, 2019
Merged

Kj recordable view refactor #471

merged 31 commits into from
Aug 12, 2019

Conversation

KJoslyn
Copy link
Contributor

@KJoslyn KJoslyn commented Jul 25, 2019

Recording, deleting, and selecting takes works on both the scripture and resource side.

Requires common #115.

To test, checkout branch kj-all-recordings-testapp. First, you will need to run Main.kt to import ulb and tn, if you haven't already. Then create a project and add an audio plugin.

Then, run

  • main in RecordScriptureTestApp.kt to work with takes on the scripture side
  • main in RecordResourceTestApp.kt to work with takes on the resource side

If you use these entry points, the app will load the first project, first chapter in that project, and first verse in that chapter. (Navigating strictly down through the app rather than using these separate entry points should work, but I created these entry points to make it faster to test the takes pages alone.)

Try performing record, delete and select (by dragging). Editing takes does not work yet due to an unknown issue with ocenaudio being unable to save edited files.

The most important part of this PR is the creation of a base class called RecordableFragment that handles all of the commonalities between the derived classes RecordScriptureFragment and RecordResourceFragment, namely dragging. Other view and viewmodels have also been renamed for clarity.

Also, I elected to keep the FlowPane as a FlowPane in RecordScriptureFragment. In lieu of finding any helpful examples of lists (particularly grids) with different data/node types, (most people were just talking about using different data types in a TableView, which doesn't solve our problem) I did some investigation:

  1. Datagrid is parameterized by a model type- we would have to create a new umbrella type to encompass the three types: "Record New Take" card, the usual take cards, and blank cards. FlowPane is simpler because you can simply add and remove nodes that can be unrelated. This avoids adding unnecessary bloat code to create a new model type for the datagrid.
  2. Datagrids (and similar lists) require an ObservableList of some kind of model. Besides point 1, this observable list would be different than but dependent on the alternateTakes list used in RecordableViewModel, which is already an ObservableList. Responding to a change in an observable list that is responds to changes in another observable list is slow (I tried it.)
  3. I investigated FlowPane.java to see how I could add or remove blank cards to always empty space in the bottom row with blank cards. This seems possible if I eventually create a copy of FlowPane with some modifications to a private method. It seemed much more complicated to do the same thing using other lists.


fun dragTargetBottom(runOnNode: (VBox.() -> Unit)? = null): Node = dragTargetBottom.apply {
runOnNode?.let { it() }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the receiver function + apply + let, this little guy is a doozy for me. Please rework for clarity. Something like this works better at least for me:

        fun dragTargetBottom(runOnNode: (VBox.() -> Unit)? = null): Node = dragTargetBottom.also {
            runOnNode?.let { dragTargetBottom.runOnNode() }
        }

Copy link
Collaborator

@jsarabia jsarabia Jul 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Single expression formats are meant to be a single expression; this is too complex for that style of formatting.

If it was fine, this would apply:
https://kotlinlang.org/docs/reference/coding-conventions.html#expression-body-formatting

I believe it was referenced in clean code though I couldn't find it, but basically, how you organize your space is important for readability. We read code top to bottom, and each line left to right. We don't read each line though, often just scan top down across the left hand side, so it's helpful when the leftmost space gives us a lot of context to what the rest of the line does, and we follow further down the line (left to right) if we need more details.

This makes a critical assumption though; that every major step begins on a new line. So in this code example, scanning top down, I see dragTargetBottom.... then the first instruction is "runonNode?..." which is actually incorrect. The fact that the closing } appears to close the function block further adds to misleading a reader just glancing at the code.

Your existing code should look like:

fun dragTargetBottom(runOnNode: (VBox.() -> Unit)? = null): Node { 
    return dragTargetBottom.apply {
        runOnNode?.let { it() }
    }
}

I know this is done elsewhere in the code; I would appreciate if we could all be on the lookout and clean them up as we come across it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, @aunger is right, this is really difficult to read; especially with overloading the variable name with the method name.

it might help to prefix the private variable with _; so _dragTargetBottom?

But the name is also really unhelpful to begin with; what is it exactly? is it the takeGridDragTarget?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to rework all of this, probably into a separate component, something like DragTakeTarget

@KJoslyn KJoslyn force-pushed the kj-recordings-viewmodel-refactor branch 2 times, most recently from 5f662a1 to e9215f5 Compare July 29, 2019 20:23
@KJoslyn KJoslyn force-pushed the kj-recordable-view-refactor branch from 427f445 to 4430ed7 Compare July 30, 2019 19:51
//selected take and drag target
stackpane {
// drag target glow
add(dragComponents
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code formatting on all of these add methods

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these add methods are going away since I'm making a new drag target component anyway

recordableTab(ContentType.BODY, 1)
)

private fun recordableTab(contentType: ContentType, sort: Int) =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use single expression function syntax here


class TakesListView(
items: ObservableList<Take>,
audioPlayer: () -> IAudioPlayer,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this a lambda?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know- that was silly :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember now- TakesListView constructs TakeCards for each take in the list, and it needed to instantiate an AudioPlayer for each of the TakeCards. In reality this parameter should have been named something like createAudioPlayer. I just pushed a commit that removes this parameter as well as lastPlayOrPauseEvent, in favor of accepting a lambda function called createTakeNode that just constructs the card given a take object.

Here's another thought- I don't know if this entire class is really needed or not. I could just create a vanilla ListView instead in RecordResourceFragment, but perhaps this class still encapsulates the takes list view and reduces the code length of RecordResourceFragment... especially with the lengthy comment explaining the lack of caching the cells.

// Take at the top to compare to an existing selected take
private var draggingTakeProperty = SimpleObjectProperty<OldTakeCard>()
class RecordScriptureFragment
: RecordableFragment(RecordableViewModelProvider().get()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you use di scopes here instead of the get?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into this, but I haven't figured out how to make it work for this situation

isFillWidth = false
val placeholder = vbox {
addClass(RecordScriptureStyles.placeholder)
add(dragComponents
Copy link
Collaborator

@jsarabia jsarabia Aug 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting on all the adds

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These adds are gone now that I created a DragTarget control

dragContainer.relocate(newX, newY)
}

private fun startDrag(evt: StartDragEvent) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we often aren't going out of our way to abbreviate things; event is already short, can we just use the full word?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure! done

false,
EventHandler {
audioPluginViewModel.addPlugin(true, false)
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these have been fixed. I also just reread parts of the Kotlin Coding Conventions to help with this, since I was unclear about proper formatting for chained call wrapping.

bottomAnchor = 0.0
topAnchor = 0.0
}
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

})

add(JFXButton("", MaterialIconView(MaterialIcon.DELETE, "18px")))
add(deleteButton.apply {
graphic = MaterialIconView(MaterialIcon.DELETE, "18px")
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

})

add(deleteButton.apply {
text = messages["delete"]
graphic = MaterialIconView(MaterialIcon.DELETE, "18px")
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

})

)
addClass(TakeCardStyles.defaultButton)
addClass(TakeCardStyles.editButton)
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

})

fireEvent(
EditTakeEvent(take) {
player.load(take.file)
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

})

@KJoslyn KJoslyn force-pushed the kj-recordings-viewmodel-refactor branch from e9215f5 to 2351c2b Compare August 7, 2019 17:30
@KJoslyn KJoslyn force-pushed the kj-recordable-view-refactor branch 2 times, most recently from b331ff4 to 1207275 Compare August 7, 2019 18:34
@KJoslyn KJoslyn force-pushed the kj-recordings-viewmodel-refactor branch from b7c9d78 to 007b69e Compare August 7, 2019 20:09
Updating alternateTakes in a selectedTakeProperty listener was too complicated since we want to leave alternateTakes alone when the user switches the recordable.
Replaced OldTakeCard with TakeCard controls. Refactored event handling for play/pause, edit, and delete. Edit still doesn't work due to problem with starting the audio player from the app. Dragging takes is also broken temporarily. Fix and view layer refactor to come.
@KJoslyn KJoslyn force-pushed the kj-recordable-view-refactor branch from 1207275 to ecd4d4a Compare August 7, 2019 20:16
/** Add custom components to this container, rather than root*/
protected val mainContainer = VBox()

protected val lastPlayOrPauseEvent: SimpleObjectProperty<PlayOrPauseEvent?> = SimpleObjectProperty()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why nullable?


class TakesFlowPane(
alternateTakes: ObservableList<Take>,
private val audioPlayer: () -> IAudioPlayer,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

audioPlayerProvider or audioPlayerBuilder... idk a lambda isn't an audio player and the name should reflect what this is

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, although I'm working on this file now and I plan to replace that parameter and the next one with a lambda function called createTakeCard that just knows how to create the take card and doesn't care about the take card's details

return vbox(10.0) {
alignment = Pos.CENTER
addClass(TakeCardStyles.scriptureTakeCardPlaceholder, TakeCardStyles.scriptureTakeCardDropShadow)
label("blank")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this need text?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't, this was just for testing. I'm going to remove that too as I'm working on TakesFlowPane now to implement the dynamic blank cards

.observeOnFx()
.subscribe { result ->
showPluginActive = false
when (result) {
EditTake.Result.NO_EDITOR -> snackBarObservable.onNext(messages["noEditor"])
EditTake.Result.SUCCESS -> {}
EditTake.Result.SUCCESS -> editTakeEvent.onComplete()
null -> {} // This cannot happen but the compiler complains if null branch does not exist
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, if you specify the type of result explicitly as non-null, this branch is unneeded. I have no problem with this as-is though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Great point.

}
}

private fun createTakeCard(
take: Take,
player: IAudioPlayer,
takeEventObservable: Observable<TakeEvent?>,
playOrPauseEventObservable: Observable<PlayOrPauseEvent?>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we had an issue putting nullables in observables?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah, this is easy to forget (and you don't find out until a null is observed at runtime).

@KJoslyn KJoslyn force-pushed the kj-recordable-view-refactor branch from 5fc53c0 to 4700f6e Compare August 12, 2019 18:11
@jsarabia jsarabia changed the base branch from kj-recordings-viewmodel-refactor to dev August 12, 2019 18:40
@jsarabia jsarabia merged commit 3fd4d36 into dev Aug 12, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants