Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,16 @@ abstract class CardViewerViewModel(

val showingAnswer = savedStateHandle.getMutableStateFlow(KEY_SHOWING_ANSWER, false)

protected val cardMediaPlayer =
protected abstract val cardMediaPlayer: CardMediaPlayer

protected fun createCardMediaPlayer(): CardMediaPlayer =
CardMediaPlayer(
javascriptEvaluator = { launchCatchingIO { eval.emit(it) } },
mediaErrorListener = mediaErrorHandler,
).also {
addCloseable(it)
}

abstract var currentCard: Deferred<Card>

abstract val server: AnkiServer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.ichi2.anki.CollectionManager.withCol
import com.ichi2.anki.Flag
import com.ichi2.anki.asyncIO
import com.ichi2.anki.browser.IdsFile
import com.ichi2.anki.cardviewer.CardMediaPlayer
import com.ichi2.anki.cardviewer.SingleCardSide
import com.ichi2.anki.common.annotations.NeedsTest
import com.ichi2.anki.launchCatchingIO
Expand All @@ -43,12 +44,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber

class PreviewerViewModel(
open class PreviewerViewModel(
savedStateHandle: SavedStateHandle,
) : CardViewerViewModel(savedStateHandle),
ChangeManager.Subscriber {
override val cardMediaPlayer by lazy { createCardMediaPlayer() }
val currentIndex =
savedStateHandle.getMutableStateFlow(
KEY_CURRENT_INDEX,
Expand Down Expand Up @@ -246,6 +249,16 @@ class PreviewerViewModel(
TypeAnswer.removeTags(text)
}

override suspend fun showQuestion() {
cardMediaPlayer.stop()
super.showQuestion()
}

override suspend fun showAnswer() {
cardMediaPlayer.stop()
super.showAnswer()
}

override fun opExecuted(
changes: OpChanges,
handler: Any?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import org.jetbrains.annotations.VisibleForTesting
class TemplatePreviewerViewModel(
savedStateHandle: SavedStateHandle,
) : CardViewerViewModel(savedStateHandle) {
override val cardMediaPlayer by lazy { createCardMediaPlayer() }
private val notetype: NotetypeJson
private val fillEmpty: Boolean
private val isCloze: Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class ReviewerViewModel(
ChangeManager.Subscriber,
BindingProcessor<ReviewerBinding, ViewerAction>,
AutoAdvance.ActionListener {
override val cardMediaPlayer by lazy { createCardMediaPlayer() }
private var queueState: Deferred<CurrentQueueState?> =
asyncIO {
withCol { sched.currentQueueState() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,22 @@ import androidx.lifecycle.SavedStateHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.Flag
import com.ichi2.anki.browser.IdsFile
import com.ichi2.anki.cardviewer.CardMediaPlayer
import com.ichi2.anki.servicelayer.NoteService
import com.ichi2.anki.utils.ext.flag
import com.ichi2.testutils.JvmTest
import com.ichi2.testutils.common.Flaky
import com.ichi2.testutils.common.OS
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
Expand All @@ -42,7 +46,13 @@ import org.junit.runner.RunWith
class PreviewerViewModelTest : JvmTest() {
private val idsFile: IdsFile = mockk()

private class TestPreviewerViewModel(
savedStateHandle: SavedStateHandle,
override val cardMediaPlayer: CardMediaPlayer,
) : PreviewerViewModel(savedStateHandle)

private lateinit var viewModel: PreviewerViewModel
private lateinit var mockMediaPlayer: CardMediaPlayer

private fun TestScope.onNextButtonClick() {
viewModel.onNextButtonClick()
Expand Down Expand Up @@ -79,9 +89,9 @@ class PreviewerViewModelTest : JvmTest() {
set(PreviewerFragment.CARD_IDS_FILE_ARG, idsFile)
}

viewModel = spyk(PreviewerViewModel(savedStateHandle))
// the default implementation requires the Collection media directory,
// which needs Robolectric with CollectionStorageMode.IN_MEMORY_WITH_MEDIA or ON_DISK
mockMediaPlayer = mockk(relaxed = true)
viewModel = spyk(TestPreviewerViewModel(savedStateHandle, mockMediaPlayer))

coEvery { viewModel.prepareCardTextForDisplay(any()) } answers { firstArg() }
}

Expand Down Expand Up @@ -270,4 +280,26 @@ class PreviewerViewModelTest : JvmTest() {
onSliderChange(sliderPosition = 2)
assertEquals("Index should update for valid input", 1, viewModel.currentIndex.value)
}

@Test
fun `audio stops when changing sides`() =
runTest {
viewModel.onPageFinished(false)
viewModel.showingAnswer.value = false

// 1. Next Button (Question -> Answer)
onNextButtonClick()
coVerify(atLeast = 1) { mockMediaPlayer.stop() }

// Reset mocks
io.mockk.clearMocks(mockMediaPlayer, answers = false, recordedCalls = true, childMocks = false)

onPreviousButtonClick()
coVerify(atLeast = 1) { mockMediaPlayer.stop() }

io.mockk.clearMocks(mockMediaPlayer, answers = false, recordedCalls = true, childMocks = false)

toggleBackSideOnly()
coVerify(atLeast = 1) { mockMediaPlayer.stop() }
}
}
Loading