0
0
mirror of https://github.com/ankidroid/Anki-Android.git synced 2024-09-19 19:42:17 +02:00

refactor: use Context receivers to remove this within withCol [Experimental API] (#15254)

* chore: enable context receivers (experimental)

Allows removal of usages of `this` in `withCol`/`undoableOp` calls

Context receivers allow `this` as an implicit parameter to a method
 if `this` is in scope (typically due to a `with` call)

This means that:
* methods are only usable if `Collection` is scoped
  * you can't easily call these methods outside `withCol`
* methods can effectively accept two context receivers
  * in the below, both `Card` and `Collection` are scoped to `this`

Example

```kotlin
context(Collection)
fun Card.note() = this.note(this@Collection)
```

allows the transformation

```diff
- val note = withCol { card.note(this) }
+ val note = withCol { card.note() }
```

Which is much more readable, as `this` was confusingly named

https://github.com/Kotlin/KEEP/blob/master/proposals/context-receivers.md
https://blog.rockthejvm.com/kotlin-context-receivers/

* chore: context receiver for Card.renderOutput()

Once refactoring is complete, the `context` should be moved to the
 instance method, and the extension should be removed

* chore: context receiver for Card.note()

Once refactoring is complete, the `context` should be moved to the
 instance method, and the extension should be removed

* chore: context receiver for AnswerTimer.resume()
* chore: context receiver for CardTemplateNotetype.saveToDatabase
* chore: context receiver for Sequence<CardId>.toCardCache
* chore: context receiver for BackupManager.repairCollection()
* chore: context receiver for CollectionOperations.deleteMedia
* chore: context receiver for CollectionOperations.updateValuesFromDeck
* chore: context receiver for CollectionOperations.saveModel
* modifies saveToDatabase, as this always required the context
* chore: remove completed comment in CollectionOperations
* chore: context receiver for CardsOrNotes.fromCollection
* chore: context receiver for CardService.selectedNoteIds
+ accept CardId
* chore: context receiver for CardsOrNotes.saveToCollection
* chore: context receiver for BackupManager.deleteBackups()
* chore: context receiver for CardSoundConfig.create()
* add context receiver
* inline suspend fun create
  * make the `col` usage explicit
* review request: import 'Collection'
This commit is contained in:
David Allison 2024-01-22 00:51:12 +00:00 committed by GitHub
parent da41ac1cb1
commit 672b075a54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 98 additions and 85 deletions

View File

@ -432,7 +432,7 @@ abstract class AbstractFlashcardViewer :
val card = editorCard!!
withProgress {
undoableOp {
updateNote(card.note(this))
updateNote(card.note())
}
}
onCardUpdated(card)
@ -477,7 +477,7 @@ abstract class AbstractFlashcardViewer :
// despite that making no sense outside of Reviewer.kt
currentCard = withCol {
sched.card?.apply {
renderOutput(this@withCol)
renderOutput()
}
}
}

View File

@ -262,15 +262,15 @@ open class BackupManager {
* Run the sqlite3 command-line-tool (if it exists) on the collection to dump to a text file
* and reload as a new database. Recently this command line tool isn't available on many devices
*
* @param col Collection
* @return whether the repair was successful
*/
fun repairCollection(col: Collection): Boolean {
val colPath = col.path
context (Collection)
fun repairCollection(): Boolean {
val colPath = this@Collection.path
val colFile = File(colPath)
val time = TimeManager.time
Timber.i("BackupManager - RepairCollection - Closing Collection")
col.close()
this@Collection.close()
// repair file
val execString = "sqlite3 $colPath .dump | sqlite3 $colPath.tmp"
@ -457,9 +457,10 @@ open class BackupManager {
*
* @return Whether all specified backups were successfully deleted.
*/
context (Collection)
@Throws(IllegalArgumentException::class)
fun deleteBackups(collection: Collection, backupsToDelete: List<File>): Boolean {
val allBackups = getBackups(File(collection.path))
fun deleteBackups(backupsToDelete: List<File>): Boolean {
val allBackups = getBackups(File(this@Collection.path))
val invalidBackupsToDelete = backupsToDelete.toSet() - allBackups.toSet()
if (invalidBackupsToDelete.isNotEmpty()) {

View File

@ -89,6 +89,7 @@ import com.ichi2.anki.widgets.DeckDropDownAdapter.SubtitleListener
import com.ichi2.annotations.NeedsTest
import com.ichi2.async.*
import com.ichi2.libanki.*
import com.ichi2.libanki.Collection
import com.ichi2.ui.CardBrowserSearchView
import com.ichi2.ui.FixedTextView
import com.ichi2.utils.*
@ -515,7 +516,7 @@ open class CardBrowser :
}
// Finish initializing the activity after the collection has been correctly loaded
override fun onCollectionLoaded(col: com.ichi2.libanki.Collection) {
override fun onCollectionLoaded(col: Collection) {
super.onCollectionLoaded(col)
Timber.d("onCollectionLoaded()")
registerExternalStorageListener()
@ -1523,7 +1524,7 @@ open class CardBrowser :
private suspend fun editSelectedCardsTags(selectedTags: List<String>, indeterminateTags: List<String>) = withProgress {
undoableOp {
val selectedNotes = selectedCardIds
.map { cardId -> getCard(cardId).note(this) }
.map { cardId -> getCard(cardId).note() }
.distinct()
.onEach { note ->
val previousTags: List<String> = note.tags
@ -1563,7 +1564,7 @@ open class CardBrowser :
val card = cardBrowserCard!!
withProgress {
undoableOp {
updateNote(card.note(this))
updateNote(card.note())
}
}
updateCardInList(card)
@ -1573,7 +1574,7 @@ open class CardBrowser :
* Removes cards from view. Doesn't delete them in model (database).
* @param reorderCards Whether to rearrange the positions of checked items (DEFECT: Currently deselects all)
*/
private fun removeNotesView(cardsIds: Collection<Long>, reorderCards: Boolean) {
private fun removeNotesView(cardsIds: List<Long>, reorderCards: Boolean) {
val idToPos = viewModel.cardIdToPositionMap
val idToRemove = cardsIds.filter { cId -> idToPos.containsKey(cId) }
mReloadRequired = mReloadRequired || cardsIds.contains(reviewerCardId)
@ -1940,7 +1941,7 @@ open class CardBrowser :
override var position: Int
private val inCardMode: Boolean
constructor(id: Long, col: com.ichi2.libanki.Collection, position: Int, cardsOrNotes: CardsOrNotes) : super(col, id) {
constructor(id: Long, col: Collection, position: Int, cardsOrNotes: CardsOrNotes) : super(col, id) {
this.position = position
this.inCardMode = cardsOrNotes == CARDS
}
@ -2259,13 +2260,14 @@ suspend fun searchForCards(
): MutableList<CardBrowser.CardCache> {
return withCol {
(if (cardsOrNotes == CARDS) findCards(query, order) else findOneCardByNote(query, order)).asSequence()
.toCardCache(this, cardsOrNotes)
.toCardCache(cardsOrNotes)
.toMutableList()
}
}
private fun Sequence<CardId>.toCardCache(col: com.ichi2.libanki.Collection, isInCardMode: CardsOrNotes): Sequence<CardBrowser.CardCache> {
return this.mapIndexed { idx, cid -> CardBrowser.CardCache(cid, col, idx, isInCardMode) }
context (Collection)
private fun Sequence<CardId>.toCardCache(isInCardMode: CardsOrNotes): Sequence<CardBrowser.CardCache> {
return this.mapIndexed { idx, cid -> CardBrowser.CardCache(cid, this@Collection, idx, isInCardMode) }
}
class Previewer2Destination(val currentIndex: Int, val selectedCardIds: LongArray)

View File

@ -547,9 +547,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener {
}
launchCatchingTask(resources.getString(R.string.card_template_editor_save_error)) {
requireActivity().withProgress(resources.getString(R.string.saving_model)) {
withCol {
tempModel!!.saveToDatabase(this)
}
withCol { tempModel!!.saveToDatabase() }
}
onModelSaved()
}

View File

@ -22,6 +22,7 @@ import androidx.core.os.bundleOf
import com.ichi2.async.saveModel
import com.ichi2.compat.CompatHelper.Companion.compat
import com.ichi2.compat.CompatHelper.Companion.getSerializableCompat
import com.ichi2.libanki.Collection
import com.ichi2.libanki.NoteTypeId
import com.ichi2.libanki.NotetypeJson
import com.ichi2.utils.KotlinCleanup
@ -87,11 +88,12 @@ class CardTemplateNotetype(val notetype: NotetypeJson) {
addTemplateChange(ChangeType.DELETE, ord)
}
fun saveToDatabase(collection: com.ichi2.libanki.Collection) {
context(Collection)
fun saveToDatabase() {
Timber.d("saveToDatabase() called")
dumpChanges()
clearTempModelFiles()
return saveModel(collection, notetype, adjustedTemplateChanges)
return saveModel(notetype, adjustedTemplateChanges)
}
/**

View File

@ -1559,7 +1559,7 @@ open class DeckPicker :
withCol {
Timber.i("RepairCollection: Closing collection")
close()
BackupManager.repairCollection(this)
BackupManager.repairCollection()
}
}
if (!result) {
@ -1618,7 +1618,7 @@ open class DeckPicker :
launchCatchingTask {
// Number of deleted files
val noOfDeletedFiles = withProgress(resources.getString(R.string.delete_media_message)) {
withCol { deleteMedia(this, unused) }
withCol { deleteMedia(unused) }
}
showSimpleMessageDialog(
title = resources.getString(R.string.delete_media_result_title),
@ -2127,7 +2127,7 @@ open class DeckPicker :
Timber.d("rebuildFiltered: doInBackground - RebuildCram")
decks.select(did)
sched.rebuildDyn(decks.selected())
updateValuesFromDeck(this)
updateValuesFromDeck()
}
}
updateDeckList()
@ -2142,7 +2142,7 @@ open class DeckPicker :
withCol {
Timber.d("doInBackgroundEmptyCram")
sched.emptyDyn(decks.selected())
updateValuesFromDeck(this)
updateValuesFromDeck()
}
}
updateDeckList()

View File

@ -202,7 +202,7 @@ open class Reviewer :
override fun onResume() {
when {
stopTimerOnAnswer && isDisplayingAnswer -> {}
else -> launchCatchingTask { withCol { answerTimer.resume(this) } }
else -> launchCatchingTask { withCol { answerTimer.resume() } }
}
super.onResume()
if (typeAnswer?.autoFocusEditText() == true) {
@ -1015,7 +1015,7 @@ open class Reviewer :
override suspend fun updateCurrentCard() {
val state = withCol {
sched.currentQueueState()?.apply {
topCard.renderOutput(this@withCol, true)
topCard.renderOutput(true)
}
}
state?.timeboxReached?.let { dealWithTimeBox(it) }

View File

@ -283,7 +283,7 @@ class StudyOptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
withCol {
Timber.d("doInBackground - RebuildCram")
sched.rebuildDyn(decks.selected())
updateValuesFromDeck(this)
updateValuesFromDeck()
}
}
rebuildUi(result, true)
@ -295,7 +295,7 @@ class StudyOptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
withCol {
Timber.d("doInBackgroundEmptyCram")
sched.emptyDyn(decks.selected())
updateValuesFromDeck(this)
updateValuesFromDeck()
}
}
rebuildUi(result, true)
@ -446,7 +446,7 @@ class StudyOptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
// Load the deck counts for the deck from Collection asynchronously
updateValuesFromDeckJob = launchCatchingTask {
if (CollectionManager.isOpenUnsafe()) {
val result = withCol { updateValuesFromDeck(this) }
val result = withCol { updateValuesFromDeck() }
rebuildUi(result, resetDecklist)
}
}

View File

@ -209,7 +209,7 @@ class CardBrowserViewModel(
viewModelScope.launch {
// PERF: slightly inefficient if the source was lastDeckId
setDeckId(getInitialDeck())
val cardsOrNotes = withCol { CardsOrNotes.fromCollection(this) }
val cardsOrNotes = withCol { CardsOrNotes.fromCollection() }
cardsOrNotesFlow.update { cardsOrNotes }
withCol {
@ -260,7 +260,7 @@ class CardBrowserViewModel(
fun setCardsOrNotes(newValue: CardsOrNotes) = viewModelScope.launch {
withCol {
// Change this to only change the preference on a state change
newValue.saveToCollection(this)
newValue.saveToCollection()
}
cardsOrNotesFlow.update { newValue }
}
@ -363,7 +363,7 @@ class CardBrowserViewModel(
CARDS -> Pair(ExportDialogFragment.ExportType.Cards, selectedCardIds)
NOTES -> Pair(
ExportDialogFragment.ExportType.Notes,
withCol { CardService.selectedNoteIds(selectedCardIds, this) }
withCol { CardService.selectedNoteIds(selectedCardIds) }
)
}
}

View File

@ -18,7 +18,6 @@ package com.ichi2.anki.cardviewer
import androidx.annotation.CheckResult
import com.ichi2.anki.CardUtils
import com.ichi2.anki.CollectionManager.withCol
import com.ichi2.libanki.Card
import com.ichi2.libanki.Collection
import com.ichi2.libanki.DeckId
@ -38,10 +37,11 @@ class CardSoundConfig(val replayQuestion: Boolean, val autoplay: Boolean, val de
fun appliesTo(card: Card): Boolean = CardUtils.getDeckIdForCard(card) == deckId
companion object {
context(Collection)
@CheckResult
fun create(collection: Collection, card: Card): CardSoundConfig {
fun create(card: Card): CardSoundConfig {
Timber.v("start loading SoundConfig")
val deckConfig = collection.decks.confForDid(CardUtils.getDeckIdForCard(card))
val deckConfig = this@Collection.decks.confForDid(CardUtils.getDeckIdForCard(card))
val autoPlay = deckConfig.optBoolean("autoplay", false)
@ -51,8 +51,5 @@ class CardSoundConfig(val replayQuestion: Boolean, val autoplay: Boolean, val de
Timber.d("loaded SoundConfig: %s", this)
}
}
@CheckResult
suspend fun create(card: Card): CardSoundConfig = withCol { create(this, card) }
}
}

View File

@ -30,6 +30,7 @@ import com.ichi2.anki.AbstractFlashcardViewer
import com.ichi2.anki.AndroidTtsError
import com.ichi2.anki.AndroidTtsError.TtsErrorCode
import com.ichi2.anki.AndroidTtsPlayer
import com.ichi2.anki.CollectionManager.withCol
import com.ichi2.anki.cardviewer.SoundErrorBehavior.CONTINUE_AUDIO
import com.ichi2.anki.cardviewer.SoundErrorBehavior.RETRY_AUDIO
import com.ichi2.anki.cardviewer.SoundErrorBehavior.STOP_AUDIO
@ -122,7 +123,7 @@ class SoundPlayer(
this.side = side
if (!this::config.isInitialized || !config.appliesTo(card)) {
config = CardSoundConfig.create(card)
config = withCol { CardSoundConfig.create(card) }
}
}

View File

@ -42,7 +42,7 @@ suspend fun rebuildCram(listener: CreateCustomStudySessionListener) {
CollectionManager.withCol {
Timber.d("doInBackground - rebuildCram()")
sched.rebuildDyn(decks.selected())
updateValuesFromDeck(this)
updateValuesFromDeck()
}
listener.onPostExecute()
}

View File

@ -28,13 +28,15 @@ enum class CardsOrNotes {
CARDS,
NOTES;
fun saveToCollection(collection: Collection) {
collection.config.setBool(ConfigKey.Bool.BROWSER_TABLE_SHOW_NOTES_MODE, this == NOTES)
context (Collection)
fun saveToCollection() {
this@Collection.config.setBool(ConfigKey.Bool.BROWSER_TABLE_SHOW_NOTES_MODE, this == NOTES)
}
companion object {
fun fromCollection(col: Collection): CardsOrNotes =
when (col.config.getBool(ConfigKey.Bool.BROWSER_TABLE_SHOW_NOTES_MODE)) {
context (Collection)
fun fromCollection(): CardsOrNotes =
when (this@Collection.config.getBool(ConfigKey.Bool.BROWSER_TABLE_SHOW_NOTES_MODE)) {
true -> NOTES
false -> CARDS
}

View File

@ -31,6 +31,7 @@ import com.ichi2.anki.servicelayer.MARKED_TAG
import com.ichi2.anki.servicelayer.NoteService
import com.ichi2.libanki.Card
import com.ichi2.libanki.Sound.addPlayButtons
import com.ichi2.libanki.note
import com.ichi2.themes.Themes
import com.ichi2.utils.toRGBHex
import kotlinx.coroutines.flow.MutableSharedFlow
@ -90,8 +91,7 @@ class PreviewerViewModel(private val selectedCardIds: LongArray, firstIndex: Int
fun toggleMark() {
launchCatching {
// TODO: Consider a context receiver
val note = withCol { currentCard.note(this) }
val note = withCol { currentCard.note() }
NoteService.toggleMark(note)
isMarked.emit(NoteService.isMarked(note))
}
@ -132,7 +132,7 @@ class PreviewerViewModel(private val selectedCardIds: LongArray, firstIndex: Int
}
private suspend fun updateMarkIcon() {
val note = withCol { currentCard.note(this) }
val note = withCol { currentCard.note() }
isMarked.emit(note.hasTag(MARKED_TAG))
}
@ -328,7 +328,7 @@ class PreviewerViewModel(private val selectedCardIds: LongArray, firstIndex: Int
private suspend fun getExpectedTypeInAnswer(card: Card, field: JSONObject): String? {
val fieldName = field.getString("name")
val expected = withCol { card.note(this).getItem(fieldName) }
val expected = withCol { card.note().getItem(fieldName) }
return if (fieldName.startsWith("cloze:")) {
val clozeIdx = card.ord + 1
withCol {

View File

@ -135,3 +135,9 @@ class AnswerTimer(private val cardTimer: Chronometer) {
private val elapsedRealTime
get() = SystemClock.elapsedRealtime()
}
/** @see AnswerTimer.resume */
context (Collection)
fun AnswerTimer.resume() {
this@AnswerTimer.resume(this@Collection)
}

View File

@ -17,6 +17,7 @@
package com.ichi2.anki.servicelayer
import com.ichi2.anki.CardUtils
import com.ichi2.libanki.CardId
import com.ichi2.libanki.Collection
object CardService {
@ -24,11 +25,11 @@ object CardService {
* get unique note ids from a list of card ids
* @param selectedCardIds list of card ids
* can do better with performance here
* TODO: blocks the UI, should be fixed
*/
fun selectedNoteIds(selectedCardIds: List<Long>, col: Collection) =
context (Collection)
fun selectedNoteIds(selectedCardIds: List<CardId>) =
CardUtils.getNotes(
col,
selectedCardIds.map { col.getCard(it) }
this@Collection,
selectedCardIds.map { this@Collection.getCard(it) }
).map { it.id }
}

View File

@ -84,9 +84,9 @@ class ManageSpaceViewModel(val app: Application) : AndroidViewModel(app), Collec
}
}
suspend fun deleteMedia(filesNamesToDelete: List<String>) {
suspend fun deleteMediaFiles(filesNamesToDelete: List<String>) {
try {
withCol { deleteMedia(this, filesNamesToDelete) }
withCol { deleteMedia(filesNamesToDelete) }
} finally {
launchCalculationOfSizeOfEverything()
launchCalculationOfCollectionSize()
@ -108,7 +108,7 @@ class ManageSpaceViewModel(val app: Application) : AndroidViewModel(app), Collec
suspend fun deleteBackups(backupsToDelete: List<File>) {
try {
withCol { BackupManager.deleteBackups(this, backupsToDelete) }
withCol { BackupManager.deleteBackups(backupsToDelete) }
} finally {
launchCalculationOfBackupsSize()
launchCalculationOfCollectionSize()
@ -225,7 +225,7 @@ class ManageSpaceFragment : SettingsFragment() {
val filesNamesToDelete = unusedFileNames.filterIndexed { index, _ -> checkedItems[index] }
withProgress(R.string.delete_media_message) {
viewModel.deleteMedia(filesNamesToDelete)
viewModel.deleteMediaFiles(filesNamesToDelete)
}
}
} else {

View File

@ -27,33 +27,23 @@ import timber.log.Timber
import java.util.*
/**
* This file contains functions that have been migrated from [CollectionTask]
* Remove this comment when migration has been completed
* TODO: All functions associated to Collection can be converted to extension function to avoid redundant parameter [col] in each.
*/
/**
* Takes a list of media file names and removes them from the Collection
* @param col Collection from which media is to be deleted
* Takes a list of media file names and removes them from the [Collection]
* @param unused List of media names to be deleted
*/
fun deleteMedia(
col: Collection,
unused: List<String>
): Int {
context (Collection)
fun deleteMedia(unused: List<String>): Int {
// FIXME: this provides progress info that is not currently used
col.media.removeFiles(unused)
this@Collection.media.removeFiles(unused)
return unused.size
}
// TODO: Once [com.ichi2.async.CollectionTask.RebuildCram] and [com.ichi2.async.CollectionTask.EmptyCram]
// are migrated to Coroutines, move this function to [com.ichi2.anki.StudyOptionsFragment]
fun updateValuesFromDeck(
col: Collection
): StudyOptionsFragment.DeckStudyData? {
context (Collection)
fun updateValuesFromDeck(): StudyOptionsFragment.DeckStudyData? {
Timber.d("doInBackgroundUpdateValuesFromDeck")
return try {
val sched = col.sched
val sched = this@Collection.sched
val counts = sched.counts()
val totalNewCount = sched.totalNewForCurrentDeck()
val totalCount = sched.cardCount()
@ -125,13 +115,13 @@ suspend fun renderBrowserQA(
* Handles everything for a model change at once - template add / deletes as well as content updates
* @return Pair<Boolean, String> : (true, null) when success, (false, exceptionMessage) when failure
*/
context (Collection)
fun saveModel(
col: Collection,
notetype: NotetypeJson,
templateChanges: ArrayList<Array<Any>>
) {
Timber.d("doInBackgroundSaveModel")
val oldModel = col.notetypes.get(notetype.getLong("id"))
val oldModel = this@Collection.notetypes.get(notetype.getLong("id"))
// TODO: make undoable
val newTemplates = notetype.getJSONArray("tmpls")
@ -140,11 +130,11 @@ fun saveModel(
when (change[1] as CardTemplateNotetype.ChangeType) {
CardTemplateNotetype.ChangeType.ADD -> {
Timber.d("doInBackgroundSaveModel() adding template %s", change[0])
col.notetypes.addTemplate(oldModel, newTemplates.getJSONObject(change[0] as Int))
this@Collection.notetypes.addTemplate(oldModel, newTemplates.getJSONObject(change[0] as Int))
}
CardTemplateNotetype.ChangeType.DELETE -> {
Timber.d("doInBackgroundSaveModel() deleting template currently at ordinal %s", change[0])
col.notetypes.remTemplate(oldModel, oldTemplates.getJSONObject(change[0] as Int))
this@Collection.notetypes.remTemplate(oldModel, oldTemplates.getJSONObject(change[0] as Int))
}
}
}
@ -152,6 +142,6 @@ fun saveModel(
// required for Rust: the modified time can't go backwards, and we updated the model by adding fields
// This could be done better
notetype.put("mod", oldModel!!.getLong("mod"))
col.notetypes.save(notetype)
col.notetypes.update(notetype)
this@Collection.notetypes.save(notetype)
this@Collection.notetypes.update(notetype)
}

View File

@ -531,3 +531,13 @@ open class Card : Cloneable {
}
}
}
/** @see Card.renderOutput */
context (Collection)
fun Card.renderOutput(reload: Boolean = false, browser: Boolean = false) =
this@Card.renderOutput(this@Collection, reload, browser)
/** @see Card.note */
context (Collection)
fun Card.note() =
this@Card.note(this@Collection)

View File

@ -17,8 +17,9 @@
package com.ichi2.anki.cardviewer
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.CollectionManager.withCol
import com.ichi2.libanki.Card
import com.ichi2.testutils.JvmTest
import kotlinx.coroutines.test.runTest
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.Ignore
@ -34,7 +35,7 @@ class CardSoundConfigTest : JvmTest() {
// defaults as-of Anki Desktop 23.10 (51a10f09)
val note = addNoteUsingBasicModel()
val card = note.firstCard()
CardSoundConfig.create(card).run {
createCardSoundConfig(card).run {
assertThat("deckId", deckId, equalTo(card.did))
// Anki Desktop: "Skip question when replaying answer" -> false
// our variable is reversed, so true
@ -49,7 +50,7 @@ class CardSoundConfigTest : JvmTest() {
fun `cards from the same note are equal`() = runTest {
val note = addNoteUsingBasicAndReversedModel()
val (card1, card2) = note.cards()
CardSoundConfig.create(card1).run {
createCardSoundConfig(card1).run {
assertThat("same note", this.appliesTo(card2))
}
}
@ -57,7 +58,7 @@ class CardSoundConfigTest : JvmTest() {
@Test
fun `cards from the same deck are equal`() = runTest {
val (note1, note2) = addNotes(count = 2)
CardSoundConfig.create(note1.firstCard()).run {
createCardSoundConfig(note1.firstCard()).run {
assertThat("same note", this.appliesTo(note2.firstCard()))
}
}
@ -66,4 +67,6 @@ class CardSoundConfigTest : JvmTest() {
@Test
fun `cards with the same deck options are equal`() {
}
private suspend fun createCardSoundConfig(card: Card) = withCol { CardSoundConfig.create(card) }
}

View File

@ -96,7 +96,7 @@ subprojects {
tasks.withType(KotlinCompile).configureEach {
compilerOptions {
allWarningsAsErrors = fatalWarnings
def compilerArgs = ['-Xjvm-default=all']
def compilerArgs = ['-Xjvm-default=all', '-Xcontext-receivers']
if (project.name != "api") {
compilerArgs += ['-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi']
}