diff --git a/.rive_head b/.rive_head index ff89b37a..ceb6b9f5 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -0cdfd3bf3286dc9314f31a6ae3f4449eec516ab3 +4107e94902aa6edefde0436b923b06dad4120bea diff --git a/kotlin/build.gradle b/kotlin/build.gradle index ce950906..2db3ca19 100644 --- a/kotlin/build.gradle +++ b/kotlin/build.gradle @@ -60,6 +60,7 @@ dependencies { androidTestImplementation "androidx.test.ext:junit-ktx:1.2.1" androidTestImplementation "androidx.test:runner:1.6.2" + androidTestImplementation "org.jetbrains.kotlin:kotlin-test-junit:2.1.0" } apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" diff --git a/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveEventTest.kt b/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveEventTest.kt index a8fc0969..27ea1d14 100644 --- a/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveEventTest.kt +++ b/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveEventTest.kt @@ -3,10 +3,11 @@ package app.rive.runtime.kotlin.core import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.internal.runner.junit4.statement.UiThreadStatement import app.rive.runtime.kotlin.test.R -import org.junit.Assert +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import kotlin.test.assertIs @RunWith(AndroidJUnit4::class) class RiveEventTest { @@ -14,7 +15,6 @@ class RiveEventTest { private val appContext = testUtils.context private lateinit var view: TestUtils.MockRiveAnimationView - @Before fun init() { view = TestUtils.MockRiveAnimationView(appContext) @@ -25,28 +25,26 @@ class RiveEventTest { UiThreadStatement.runOnUiThread { val observer = TestUtils.EventObserver() view.setRiveResource(R.raw.events_test, stateMachineName = "State Machine 1") - view.addEventListener(observer); - view.play() + view.addEventListener(observer) view.fireState("State Machine 1", "FireGeneralEvent") // This advance processes the fire but because we internally grab the latest events // before advancing the artboard, we don't catch "this frame's" events until the next // advance, which is why we advance twice here. - view.artboardRenderer?.advance(0.016f); + view.artboardRenderer?.advance(0.016f) + // No events yet. - Assert.assertEquals(0, observer.events.size) - view.artboardRenderer?.advance(0.016f); - view.artboardRenderer?.advance(0.016f); + assertEquals(0, observer.events.size) + // Second advance reports the event triggered by fireState. - Assert.assertEquals(1, observer.events.size) - val event = observer.events[0]; - Assert.assertTrue(event is RiveGeneralEvent) - Assert.assertEquals("SomeGeneralEvent", event.name) - Assert.assertEquals(EventType.GeneralEvent, event.type) + view.artboardRenderer?.advance(0.016f) + assertEquals(1, observer.events.size) + val event = observer.events[0] + assertIs(event) + assertEquals("SomeGeneralEvent", event.name) + assertEquals(EventType.GeneralEvent, event.type) val expectedProperties = hashMapOf("SomeNumber" to 11.0f, "SomeString" to "Something", "SomeBoolean" to true) - Assert.assertEquals(expectedProperties, event.properties) - view.artboardRenderer?.advance(0.016f); - + assertEquals(expectedProperties, event.properties) } } @@ -55,21 +53,21 @@ class RiveEventTest { UiThreadStatement.runOnUiThread { val observer = TestUtils.EventObserver() view.setRiveResource(R.raw.events_test, stateMachineName = "State Machine 1") - view.addEventListener(observer); + view.addEventListener(observer) view.play() view.fireState("State Machine 1", "FireOpenUrlEvent") - view.artboardRenderer?.advance(0.016f); + view.artboardRenderer?.advance(0.016f) // Events caught on second advance. - view.artboardRenderer?.advance(0.016f); - Assert.assertEquals(1, observer.events.size) - val event = observer.events[0]; - Assert.assertTrue(event is RiveOpenURLEvent) - Assert.assertEquals("SomeOpenUrlEvent", event.name) - Assert.assertEquals(EventType.OpenURLEvent, event.type) - Assert.assertEquals("https://rive.app", (event as RiveOpenURLEvent).url) - Assert.assertEquals("_parent", (event).target) - val expectedProperties = hashMapOf(); - Assert.assertEquals(expectedProperties, event.properties) + view.artboardRenderer?.advance(0.016f) + assertEquals(1, observer.events.size) + val event = observer.events[0] + assertIs(event) + assertEquals("SomeOpenUrlEvent", event.name) + assertEquals(EventType.OpenURLEvent, event.type) + assertEquals("https://rive.app", (event as RiveOpenURLEvent).url) + assertEquals("_parent", (event).target) + val expectedProperties = hashMapOf() + assertEquals(expectedProperties, event.properties) } } @@ -78,12 +76,12 @@ class RiveEventTest { UiThreadStatement.runOnUiThread { val observer = TestUtils.EventObserver() view.setRiveResource(R.raw.events_test, stateMachineName = "State Machine 1") - view.addEventListener(observer); + view.addEventListener(observer) view.play() view.fireState("State Machine 1", "FireBothEvents") - view.artboardRenderer?.advance(0.016f); - view.artboardRenderer?.advance(0.016f); - Assert.assertEquals(2, observer.events.size) + view.artboardRenderer?.advance(0.016f) + view.artboardRenderer?.advance(0.016f) + assertEquals(2, observer.events.size) } } -} \ No newline at end of file +} diff --git a/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveTextValueRunTest.kt b/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveTextValueRunTest.kt index 3c2db415..4a7568a1 100644 --- a/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveTextValueRunTest.kt +++ b/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveTextValueRunTest.kt @@ -3,15 +3,14 @@ package app.rive.runtime.kotlin.core import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.internal.runner.junit4.statement.UiThreadStatement import app.rive.runtime.kotlin.RiveAnimationView -import app.rive.runtime.kotlin.core.errors.RiveException import app.rive.runtime.kotlin.core.errors.TextValueRunException import app.rive.runtime.kotlin.test.R import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue -import org.junit.Assert.fail import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) @@ -27,7 +26,7 @@ class RiveTextValueRunTest { file = File( appContext.resources.openRawResource(R.raw.hello_world_text).readBytes() ) - nestePathFile= File( + nestePathFile = File( appContext.resources.openRawResource(R.raw.runtime_nested_text_runs).readBytes() ) @@ -36,7 +35,7 @@ class RiveTextValueRunTest { @Test fun read_and_update_text_run() { - var artboard = file.firstArtboard; + var artboard = file.firstArtboard assertEquals(artboard.dependencies.count(), 0) val textRun = artboard.textRun("name") @@ -54,12 +53,12 @@ class RiveTextValueRunTest { // Setting through the helper method updateValue = "new value" - artboard.setTextRunValue("name",updateValue) + artboard.setTextRunValue("name", updateValue) assertEquals(updateValue, textRun.text) assertEquals(updateValue, artboard.getTextRunValue("name")) // Only accessing .textRun should add to the dependencies. - assertEquals(artboard.dependencies.count(), 1) + assertEquals(1, artboard.dependencies.count()) } @Test(expected = TextValueRunException::class) @@ -69,22 +68,64 @@ class RiveTextValueRunTest { @Test fun read_and_update_text_run_at_path() { - val artboard = nestePathFile.firstArtboard; - assertEquals(artboard.dependencies.count(), 0) - - nestedTextRunHelper(artboard, "ArtboardBRun", "ArtboardB-1","Artboard B Run", "ArtboardB-1" ) - nestedTextRunHelper(artboard, "ArtboardBRun", "ArtboardB-2","Artboard B Run", "ArtboardB-2" ) - nestedTextRunHelper(artboard, "ArtboardCRun", "ArtboardB-1/ArtboardC-1","Artboard C Run", "ArtboardB-1/C-1" ) - nestedTextRunHelper(artboard, "ArtboardCRun", "ArtboardB-1/ArtboardC-2","Artboard C Run", "ArtboardB-1/C-2" ) - nestedTextRunHelper(artboard, "ArtboardCRun", "ArtboardB-2/ArtboardC-1","Artboard C Run", "ArtboardB-2/C-1" ) - nestedTextRunHelper(artboard, "ArtboardCRun", "ArtboardB-2/ArtboardC-2","Artboard C Run", "ArtboardB-2/C-2" ) + val artboard = nestePathFile.firstArtboard + assertEquals(0, artboard.dependencies.count()) + + nestedTextRunHelper( + artboard, + "ArtboardBRun", + "ArtboardB-1", + "Artboard B Run", + "ArtboardB-1" + ) + nestedTextRunHelper( + artboard, + "ArtboardBRun", + "ArtboardB-2", + "Artboard B Run", + "ArtboardB-2" + ) + nestedTextRunHelper( + artboard, + "ArtboardCRun", + "ArtboardB-1/ArtboardC-1", + "Artboard C Run", + "ArtboardB-1/C-1" + ) + nestedTextRunHelper( + artboard, + "ArtboardCRun", + "ArtboardB-1/ArtboardC-2", + "Artboard C Run", + "ArtboardB-1/C-2" + ) + nestedTextRunHelper( + artboard, + "ArtboardCRun", + "ArtboardB-2/ArtboardC-1", + "Artboard C Run", + "ArtboardB-2/C-1" + ) + nestedTextRunHelper( + artboard, + "ArtboardCRun", + "ArtboardB-2/ArtboardC-2", + "Artboard C Run", + "ArtboardB-2/C-2" + ) // Only accessing the textRun directly should increase the dependency. // Calling getTextRunValue and setTextRunValue should not. - assertEquals(artboard.dependencies.count(), 6) + assertEquals(6, artboard.dependencies.count()) } - private fun nestedTextRunHelper(artboard: Artboard, name: String, path: String, originalValue: String, updatedValue: String) { + private fun nestedTextRunHelper( + artboard: Artboard, + name: String, + path: String, + originalValue: String, + updatedValue: String + ) { // Get the text value run. This should increase the dependency count val textRun = artboard.textRun(name, path = path) @@ -107,74 +148,63 @@ class RiveTextValueRunTest { fun viewSetGetTextRun() { UiThreadStatement.runOnUiThread { mockView.setRiveResource(R.raw.hello_world_text, autoplay = false) - mockView.play(listOf("State Machine 1"), areStateMachines = true) - assertEquals(true, mockView.isPlaying) + mockView.play("State Machine 1", isStateMachine = true) + assertTrue(mockView.isPlaying) - assertEquals(mockView.controller.activeArtboard?.dependencies?.count(), 1) + assertEquals(1, mockView.controller.activeArtboard?.dependencies?.count()) val textValue = mockView.getTextRunValue("name") - assertEquals(textValue, "world") + assertEquals("world", textValue) - var newValue = "New Value"; + val newValue = "New Value" mockView.setTextRunValue("name", newValue) val textValueUpdated = mockView.getTextRunValue("name") - assertEquals(textValueUpdated, newValue) + assertEquals(newValue, textValueUpdated) - assertEquals(mockView.controller.activeArtboard?.dependencies?.count(), 1) + assertEquals(1, mockView.controller.activeArtboard?.dependencies?.count()) // Test for throwing an error when giving a wrong text run name - try { + val exception1 = assertFailsWith { mockView.setTextRunValue("non_existent_text_run", "Some Value") - fail("Expected an exception to be thrown") - } catch (e: Exception) { - assertTrue(e is RiveException) - assertTrue(e.message?.contains("No Rive TextValueRun found") == true) } + assertTrue(exception1.message?.contains("No Rive TextValueRun found") ?: false) // Test for throwing an error when giving a wrong text run name for a nested artboard - try { - mockView.setTextRunValue("non_existent_text_run", "Some Value", "ArtboardB-1") - fail("Expected an exception to be thrown") - } catch (e: Exception) { - assertTrue(e is RiveException) - assertTrue(e.message?.contains("No Rive TextValueRun found") == true) + val exception2 = assertFailsWith { + mockView.setTextRunValue("name", "Some Value", "non_existent_path") } + assertTrue(exception2.message?.contains("No Rive TextValueRun found") ?: false) } } + @Test fun viewSetGetNestedTextRun() { UiThreadStatement.runOnUiThread { mockView.setRiveResource(R.raw.runtime_nested_text_runs, autoplay = false) - mockView.play(listOf("State Machine 1"), areStateMachines = true) + mockView.play("State Machine 1", isStateMachine = true) assertEquals(true, mockView.isPlaying) - assertEquals(mockView.controller.activeArtboard?.dependencies?.count(), 1) + assertEquals(1, mockView.controller.activeArtboard?.dependencies?.count()) val textValue = mockView.getTextRunValue("ArtboardBRun", "ArtboardB-1") - assertEquals(textValue, "Artboard B Run") + assertEquals("Artboard B Run", textValue) - var newValue = "New Value"; - mockView.setTextRunValue("ArtboardBRun", newValue, "ArtboardB-1" ) + val newValue = "New Value" + mockView.setTextRunValue("ArtboardBRun", newValue, "ArtboardB-1") val textValueUpdated = mockView.getTextRunValue("ArtboardBRun", "ArtboardB-1") - assertEquals(textValueUpdated, newValue) + assertEquals(newValue, textValueUpdated) assertEquals(mockView.controller.activeArtboard?.dependencies?.count(), 1) // Test for throwing an error when giving a wrong text run name for a nested artboard - try { + val exception1 = assertFailsWith { mockView.setTextRunValue("non_existent_text_run", "Some Value", "ArtboardB-1") - fail("Expected an exception to be thrown") - } catch (e: Exception) { - assertTrue(e is RiveException) - assertTrue(e.message?.contains("No Rive TextValueRun found") == true) } + assertTrue(exception1.message?.contains("No Rive TextValueRun found") ?: false) // Test for throwing an error when giving a wrong path for a nested artboard - try { + val exception2 = assertFailsWith { mockView.setTextRunValue("ArtboardBRun", "Some Value", "non_existent_path") - fail("Expected an exception to be thrown") - } catch (e: Exception) { - assertTrue(e is RiveException) - assertTrue(e.message?.contains("No Rive TextValueRun found") == true) } + assertTrue(exception2.message?.contains("No Rive TextValueRun found") ?: false) } } -} \ No newline at end of file +} diff --git a/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveViewStateMachineTest.kt b/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveViewStateMachineTest.kt index e3f6fe16..e0f7ee1c 100644 --- a/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveViewStateMachineTest.kt +++ b/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveViewStateMachineTest.kt @@ -6,6 +6,7 @@ import app.rive.runtime.kotlin.RiveAnimationView import app.rive.runtime.kotlin.test.R import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before @@ -31,7 +32,7 @@ class RiveViewStateMachineTest { mockView.setRiveResource(R.raw.multiple_state_machines, autoplay = false) mockView.play(listOf("one", "two"), areStateMachines = true) - assertEquals(true, mockView.isPlaying) + assertTrue(mockView.isPlaying) assertEquals(listOf("New Artboard"), mockView.file?.artboardNames) assertEquals( listOf("one", "two"), @@ -44,10 +45,10 @@ class RiveViewStateMachineTest { fun viewDefaultsNoAutoplay() { UiThreadStatement.runOnUiThread { mockView.setRiveResource(R.raw.multiple_state_machines, autoplay = false) - assertEquals(false, mockView.isPlaying) + assertFalse(mockView.isPlaying) mockView.artboardName = "New Artboard" assertEquals( - listOf(), + emptyList(), mockView.stateMachines.map { it.name }.toList() ) } @@ -57,7 +58,7 @@ class RiveViewStateMachineTest { fun viewPause() { UiThreadStatement.runOnUiThread { mockView.setRiveResource(R.raw.multiple_state_machines, stateMachineName = "one") - assertEquals(true, mockView.isPlaying) + assertTrue(mockView.isPlaying) assertEquals(0, mockView.animations.size) assertEquals(0, mockView.playingAnimations.size) assertEquals(1, mockView.stateMachines.size) @@ -72,14 +73,14 @@ class RiveViewStateMachineTest { @Test fun viewPlayStateWithNoDuration() { UiThreadStatement.runOnUiThread { - // state machine four's has transitions that happen instantly, so we do not stick on - // a state that's playing an animation + // State machine four has transitions that happen instantly + // which when auto-played will settle immediately. mockView.setRiveResource(R.raw.multiple_state_machines, stateMachineName = "four") - assertEquals(true, mockView.isPlaying) + assertTrue(mockView.isPlaying) assertEquals(1, mockView.stateMachines.size) assertEquals(1, mockView.playingStateMachines.size) - mockView.artboardRenderer?.advance(0.016f); - assertEquals(false, mockView.isPlaying) + mockView.artboardRenderer?.advance(0.016f) + assertFalse(mockView.isPlaying) assertEquals(1, mockView.stateMachines.size) assertEquals(0, mockView.playingStateMachines.size) } @@ -89,31 +90,33 @@ class RiveViewStateMachineTest { fun viewStateMachinesPause() { UiThreadStatement.runOnUiThread { mockView.setRiveResource(R.raw.what_a_state, stateMachineName = "State Machine 2") - assert(mockView.isPlaying) - assert(mockView.artboardRenderer != null) + assertTrue(mockView.isPlaying) + assertNotNull(mockView.artboardRenderer) // Let the state machine animation run its course. mockView.artboardRenderer!!.advance(1.01f) // Must advance by non 0 to truly complete. mockView.artboardRenderer!!.advance(0.01f) - assert(!mockView.isPlaying) + assertFalse(mockView.isPlaying) } } @Test fun viewStateMachinePlayBeforeAttach() { UiThreadStatement.runOnUiThread { - val mView = mockView as TestUtils.MockRiveAnimationView - val controller = mView.controller - mView.setRiveResource(R.raw.what_a_state) - mView.play("State Machine 2", isStateMachine = true) + val view = mockView as TestUtils.MockRiveAnimationView + val controller = view.controller + view.setRiveResource(R.raw.what_a_state) + + view.play("State Machine 2", isStateMachine = true) assertEquals(1, controller.stateMachines.size) + // Scroll away: remove the view. - mView.mockDetach() + view.mockDetach() assertFalse(controller.isActive) assertNull(controller.file) // Scroll back, re-add the view. - mView.mockAttach() + view.mockAttach() assertTrue(controller.isActive) assertEquals("State Machine 2", controller.stateMachines.first().name) } @@ -123,20 +126,20 @@ class RiveViewStateMachineTest { fun nestedStateMachinesContinuePlaying() { UiThreadStatement.runOnUiThread { // The main state machine's controller is not advancing, however, nested artboards - // need to continue playing - val mView = mockView as TestUtils.MockRiveAnimationView - val controller = mView.controller - mView.setRiveResource(R.raw.nested_settle) - mView.play("State Machine 1", isStateMachine = true) - assertEquals(1, controller.stateMachines.size) + // need to continue playing. + mockView.setRiveResource(R.raw.nested_settle) + + mockView.play("State Machine 1", isStateMachine = true) + assertEquals(1, mockView.controller.stateMachines.size) + + mockView.artboardRenderer?.advance(0.5f) + assertTrue(mockView.isPlaying) + mockView.artboardRenderer?.advance(0.5f) + assertTrue(mockView.isPlaying) - mView.artboardRenderer?.advance(0.5f); - assert(mView.isPlaying) - mView.artboardRenderer?.advance(0.5f); - assert(mView.isPlaying) // The nested artboard should now be settled - mView.artboardRenderer?.advance(0.01f); - assert(!mView.isPlaying) // Playing is false + mockView.artboardRenderer?.advance(0.01f) + assertFalse(mockView.isPlaying) } } @@ -144,17 +147,14 @@ class RiveViewStateMachineTest { @Ignore("We're not stopping state machines when all layers are stopped atm.") fun viewStateMachinesInstancesRemoveOnStop() { UiThreadStatement.runOnUiThread { - val view = RiveAnimationView(appContext) view.setRiveResource(R.raw.what_a_state, stateMachineName = "State Machine 2") + assertEquals(1, view.stateMachines.size) - assert(view.artboardRenderer != null) val renderer = view.artboardRenderer!! - assertEquals(1, view.stateMachines.size) renderer.advance(2f) - assertEquals(false, view.isPlaying) + assertFalse(view.isPlaying) assertEquals(0, view.stateMachines.size) } } - -} \ No newline at end of file +} diff --git a/kotlin/src/main/cpp/src/bindings/bindings_state_machine_instance.cpp b/kotlin/src/main/cpp/src/bindings/bindings_state_machine_instance.cpp index 454397f4..6a3f39fa 100644 --- a/kotlin/src/main/cpp/src/bindings/bindings_state_machine_instance.cpp +++ b/kotlin/src/main/cpp/src/bindings/bindings_state_machine_instance.cpp @@ -18,7 +18,7 @@ extern "C" { auto stateMachineInstance = reinterpret_cast(ref); - return stateMachineInstance->advance(elapsedTime, true); + return stateMachineInstance->advanceAndApply(elapsedTime); } JNIEXPORT jint JNICALL diff --git a/kotlin/src/main/java/app/rive/runtime/kotlin/controllers/RiveFileController.kt b/kotlin/src/main/java/app/rive/runtime/kotlin/controllers/RiveFileController.kt index 5b539ee0..823057b2 100644 --- a/kotlin/src/main/java/app/rive/runtime/kotlin/controllers/RiveFileController.kt +++ b/kotlin/src/main/java/app/rive/runtime/kotlin/controllers/RiveFileController.kt @@ -342,17 +342,13 @@ class RiveFileController internal constructor( } } - // Only pause the state machines once the artboard has also settled, as nested - // artboards may still be advancing. This is to tie into the current logic of - // pausing only when current animations/machines are empty. In the future - // this can be simplified. - // Resolves: https://github.com/rive-app/rive-android/issues/338 - if (!ab.advance(elapsed) && elapsed > 0.0) { - // Only remove the state machines if the elapsed time was - // greater than 0. 0 elapsed time causes no changes so it's - // no-op advance. + // Only remove the state machines if the elapsed time was + // greater than 0. 0 elapsed time causes no changes so it's + // no-op advance. + if (elapsed > 0.0) { stateMachinesToPause.forEach { pause(stateMachine = it) } } + notifyAdvance(elapsed) } }