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

remove Kotlin's "Context Receiver" feature

Context Receivers are an experimental Kotlin feature
. They will be removed and replaced with Context Parameters.
It is unlikely that there will be any overlap period, so we need
to remove our Context Receiver code, with the intention of replacing it
at a later date

> * **2.0.20**: A warning is introduced for declarations that use
> context receivers.
> The warning should mention that the design of context receivers
> is about to change, according to the new design (add a link to the KEEP/YT).
> The safest workaround is to migrate your code to extensions.
>  * **2.1.20**: The implementation of context receivers is removed from the compiler.
> Effectively, this warning is turned into an error.
> Note that this part is most likely inevitable as the implementation
> of context parameters requires  quite extensive refactoring in the compiler,
> and there are significant chances that we won't have either
> context receivers or context parameters at that time.
>  [There is a slight chance that we'll have an early prototype of context parameters,
> but we are not counting on that.]
> * **2.2.0**: Context parameters are introduced under a new compiler flag.

https://youtrack.jetbrains.com/issue/KT-67119/Migration-warning-from-context-receivers-to-context-parameters

> **Phased removal of context receivers feature**
> In Kotlin 1.6.20, we introduced context receivers as an
> Experimental feature. After listening to community
> feedback, we've decided not to continue with it. Instead,
> we plan to introduce a replacement in future Kotlin
> releases: context parameters. You can find the proposal
> for context parameters in the KEEP.

https://kotlinlang.org/docs/whatsnew-eap.html#phased-removal-of-context-receivers-feature

Deprecated:
https://redirect.github.com/Kotlin/KEEP/blob/context-parameters/proposals/context-receivers.md
* https://redirect.github.com/Kotlin/KEEP/issues/259

To be replaced by:
https://redirect.github.com/Kotlin/KEEP/blob/context-parameters/proposals/context-parameters.md
* https://redirect.github.com/Kotlin/KEEP/issues/367

Fixes 16863

Reverts: 672b075a54
Reverts: 1c01e4cb6f
This commit is contained in:
David Allison 2024-08-14 13:52:39 +01:00
parent 35cfd4de33
commit 06e8ac2ef3
48 changed files with 165 additions and 237 deletions

View File

@ -1330,7 +1330,7 @@ class ContentProviderTest : InstrumentedTest() {
fields: Array<String>,
tag: String
): Uri {
val newNote = col.run { Note.fromNotetypeId(mid) }
val newNote = Note.fromNotetypeId(col, mid)
for (idx in fields.indices) {
newNote.setField(idx, fields[idx])
}

View File

@ -145,9 +145,6 @@ import com.ichi2.libanki.Sound.getAvTag
import com.ichi2.libanki.SoundOrVideoTag
import com.ichi2.libanki.TTSTag
import com.ichi2.libanki.Utils
import com.ichi2.libanki.note
import com.ichi2.libanki.renderOutput
import com.ichi2.libanki.setTagsFromStr
import com.ichi2.libanki.undoableOp
import com.ichi2.themes.Themes
import com.ichi2.themes.Themes.getResFromAttr
@ -519,7 +516,7 @@ abstract class AbstractFlashcardViewer :
// despite that making no sense outside of Reviewer.kt
currentCard = withCol {
sched.card?.apply {
renderOutput()
renderOutput(this@withCol, reload = false, browser = false)
}
}
}
@ -2612,7 +2609,7 @@ abstract class AbstractFlashcardViewer :
val tags = ArrayList(getColUnsafe.tags.all())
val selTags = ArrayList(currentCard!!.note(getColUnsafe).tags)
val dialog = tagsDialogFactory!!.newTagsDialog()
.withArguments(TagsDialog.DialogType.EDIT_TAGS, selTags, tags)
.withArguments(this, TagsDialog.DialogType.EDIT_TAGS, selTags, tags)
showDialogFragment(dialog)
}
@ -2622,10 +2619,10 @@ abstract class AbstractFlashcardViewer :
stateFilter: CardStateFilter
) {
launchCatchingTask {
val note = withCol { currentCard!!.note() }
val note = withCol { currentCard!!.note(this@withCol) }
if (note.tags == selectedTags) return@launchCatchingTask
withCol { note.setTagsFromStr(selectedTags.joinToString(" ")) }
withCol { note.setTagsFromStr(this@withCol, selectedTags.joinToString(" ")) }
undoableOp { updateNote(note) }
// Reload current card to reflect tag changes
reloadWebViewContent()

View File

@ -267,13 +267,12 @@ open class BackupManager {
*
* @return whether the repair was successful
*/
context (Collection)
fun repairCollection(): Boolean {
val colPath = this@Collection.path
fun repairCollection(col: Collection): Boolean {
val colPath = col.path
val colFile = File(colPath)
val time = TimeManager.time
Timber.i("BackupManager - RepairCollection - Closing Collection")
this@Collection.close()
col.close()
// repair file
val execString = "sqlite3 $colPath .dump | sqlite3 $colPath.tmp"
@ -460,10 +459,9 @@ open class BackupManager {
*
* @return Whether all specified backups were successfully deleted.
*/
context (Collection)
@Throws(IllegalArgumentException::class)
fun deleteBackups(backupsToDelete: List<File>): Boolean {
val allBackups = getBackups(File(this@Collection.path))
fun deleteBackups(collection: Collection, backupsToDelete: List<File>): Boolean {
val allBackups = getBackups(File(collection.path))
val invalidBackupsToDelete = backupsToDelete.toSet() - allBackups.toSet()
if (invalidBackupsToDelete.isNotEmpty()) {

View File

@ -128,7 +128,6 @@ import com.ichi2.libanki.SortOrder
import com.ichi2.libanki.Sound
import com.ichi2.libanki.TemplateManager
import com.ichi2.libanki.Utils
import com.ichi2.libanki.setTagsFromStr
import com.ichi2.libanki.stripAvRefs
import com.ichi2.libanki.undoableOp
import com.ichi2.libanki.utils.TimeManager
@ -886,7 +885,7 @@ open class CardBrowser :
}
actionBarMenu?.findItem(R.id.action_reschedule_cards)?.title =
TR.actionsSetDueDate().toSentenceCase(R.string.sentence_set_due_date)
TR.actionsSetDueDate().toSentenceCase(this, R.string.sentence_set_due_date)
previewItem = menu.findItem(R.id.action_preview)
onSelectionChanged()
@ -936,12 +935,12 @@ open class CardBrowser :
}
if (viewModel.hasSelectedAnyRows()) {
actionBarMenu.findItem(R.id.action_suspend_card).apply {
title = TR.browsingToggleSuspend().toSentenceCase(R.string.sentence_toggle_suspend)
title = TR.browsingToggleSuspend().toSentenceCase(this@CardBrowser, R.string.sentence_toggle_suspend)
// TODO: I don't think this icon is necessary
setIcon(R.drawable.ic_suspend)
}
actionBarMenu.findItem(R.id.action_toggle_bury).apply {
title = TR.browsingToggleBury().toSentenceCase(R.string.sentence_toggle_bury)
title = TR.browsingToggleBury().toSentenceCase(this@CardBrowser, R.string.sentence_toggle_bury)
}
actionBarMenu.findItem(R.id.action_mark_card).apply {
title = TR.browsingToggleMark()
@ -1347,6 +1346,7 @@ open class CardBrowser :
Timber.d("showEditTagsDialog: edit tags for one note")
tagsDialogListenerAction = TagsDialogListenerAction.EDIT_TAGS
val dialog = tagsDialogFactory.newTagsDialog().withArguments(
this@CardBrowser,
type = TagsDialog.DialogType.EDIT_TAGS,
checkedTags = checkedTags,
allTags = allTags
@ -1378,6 +1378,7 @@ open class CardBrowser :
// withArguments performs IO, can be 18 seconds
val dialog = withContext(Dispatchers.IO) {
tagsDialogFactory.newTagsDialog().withArguments(
context = this@CardBrowser,
type = TagsDialog.DialogType.EDIT_TAGS,
checkedTags = checkedTags,
uncheckedTags = uncheckedTags,
@ -1392,9 +1393,10 @@ open class CardBrowser :
private fun showFilterByTagsDialog() {
tagsDialogListenerAction = TagsDialogListenerAction.FILTER
val dialog = tagsDialogFactory.newTagsDialog().withArguments(
TagsDialog.DialogType.FILTER_BY_TAG,
ArrayList(0),
getColUnsafe.tags.all()
context = this@CardBrowser,
type = TagsDialog.DialogType.FILTER_BY_TAG,
checkedTags = ArrayList(0),
allTags = getColUnsafe.tags.all()
)
showDialogFragment(dialog)
}
@ -1552,7 +1554,7 @@ open class CardBrowser :
.onEach { note ->
val previousTags: List<String> = note.tags
val updatedTags = getUpdatedTags(previousTags, selectedTags, indeterminateTags)
note.setTagsFromStr(tags.join(updatedTags))
note.setTagsFromStr(this@undoableOp, tags.join(updatedTags))
}
updateNotes(selectedNotes)
}
@ -2339,14 +2341,13 @@ suspend fun searchForCards(
): MutableList<CardBrowser.CardCache> {
return withCol {
(if (cardsOrNotes == CARDS) findCards(query, order) else findOneCardByNote(query, order)).asSequence()
.toCardCache(cardsOrNotes)
.toCardCache(this@withCol, cardsOrNotes)
.toMutableList()
}
}
context (Collection)
private fun Sequence<CardId>.toCardCache(isInCardMode: CardsOrNotes): Sequence<CardBrowser.CardCache> {
return this.mapIndexed { idx, cid -> CardBrowser.CardCache(cid, this@Collection, idx, isInCardMode) }
private fun Sequence<CardId>.toCardCache(col: Collection, isInCardMode: CardsOrNotes): Sequence<CardBrowser.CardCache> {
return this.mapIndexed { idx, cid -> CardBrowser.CardCache(cid, col, idx, isInCardMode) }
}
class PreviewerDestination(val currentIndex: Int, val previewerIdsFile: PreviewerIdsFile)

View File

@ -191,7 +191,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener {
val notetype = tempModel!!.notetype
val notetypeFile = NotetypeFile(this@CardTemplateEditor, notetype)
val ord = viewPager.currentItem
val note = withCol { currentFragment?.getNote(this) ?: Note.fromNotetypeId(notetype.id) }
val note = withCol { currentFragment?.getNote(this) ?: Note.fromNotetypeId(this@withCol, notetype.id) }
val args = TemplatePreviewerArguments(
notetypeFile = notetypeFile,
id = note.id,
@ -736,7 +736,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 { templateEditor.tempModel!!.saveToDatabase() }
withCol { templateEditor.tempModel!!.saveToDatabase(this@withCol) }
}
onModelSaved()
}
@ -889,7 +889,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener {
val notetype = templateEditor.tempModel!!.notetype
val notetypeFile = NotetypeFile(requireContext(), notetype)
val ord = templateEditor.viewPager.currentItem
val note = withCol { getNote(this) ?: Note.fromNotetypeId(notetype.id) }
val note = withCol { getNote(this) ?: Note.fromNotetypeId(this@withCol, notetype.id) }
val args = TemplatePreviewerArguments(
notetypeFile = notetypeFile,
id = note.id,

View File

@ -91,12 +91,11 @@ class CardTemplateNotetype(val notetype: NotetypeJson) {
addTemplateChange(ChangeType.DELETE, ord)
}
context(Collection)
fun saveToDatabase() {
fun saveToDatabase(col: Collection) {
Timber.d("saveToDatabase() called")
dumpChanges()
clearTempModelFiles()
return saveModel(notetype, adjustedTemplateChanges)
return saveModel(col, notetype, adjustedTemplateChanges)
}
/**

View File

@ -1904,7 +1904,7 @@ open class DeckPicker :
withCol {
Timber.i("RepairCollection: Closing collection")
close()
BackupManager.repairCollection()
BackupManager.repairCollection(this@withCol)
}
}
if (!result) {
@ -1963,7 +1963,7 @@ open class DeckPicker :
launchCatchingTask {
// Number of deleted files
val noOfDeletedFiles = withProgress(resources.getString(R.string.delete_media_message)) {
withCol { deleteMedia(unused) }
withCol { deleteMedia(this@withCol, unused) }
}
showSimpleMessageDialog(
title = resources.getString(R.string.delete_media_result_title),

View File

@ -108,6 +108,7 @@ import com.ichi2.anki.noteeditor.CustomToolbarButton
import com.ichi2.anki.noteeditor.FieldState
import com.ichi2.anki.noteeditor.FieldState.FieldChangeType
import com.ichi2.anki.noteeditor.NoteEditorLauncher
import com.ichi2.anki.noteeditor.Toolbar
import com.ichi2.anki.noteeditor.Toolbar.TextFormatListener
import com.ichi2.anki.noteeditor.Toolbar.TextWrapper
import com.ichi2.anki.pages.ImageOcclusion
@ -139,8 +140,6 @@ import com.ichi2.libanki.NotetypeJson
import com.ichi2.libanki.Notetypes
import com.ichi2.libanki.Notetypes.Companion.NOT_FOUND_NOTE_TYPE
import com.ichi2.libanki.Utils
import com.ichi2.libanki.load
import com.ichi2.libanki.note
import com.ichi2.libanki.undoableOp
import com.ichi2.utils.ClipboardUtil
import com.ichi2.utils.HashUtil
@ -168,12 +167,10 @@ import java.io.File
import java.util.LinkedList
import java.util.Locale
import java.util.function.Consumer
import kotlin.collections.ArrayList
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
import androidx.appcompat.widget.Toolbar as MainToolbar
import com.ichi2.anki.noteeditor.Toolbar as Toolbar
/**
* Allows the user to edit a note, for instance if there is a typo. A card is a presentation of a note, and has two
@ -1175,7 +1172,7 @@ class NoteEditor : AnkiFragment(R.layout.note_editor), DeckSelectionListener, Su
if (caller == CALLER_PREVIEWER_EDIT || caller == CALLER_EDIT) {
withProgress {
undoableOp {
updateNote(currentEditedCard!!.note())
updateNote(currentEditedCard!!.note(this@undoableOp))
}
}
}
@ -1196,7 +1193,7 @@ class NoteEditor : AnkiFragment(R.layout.note_editor), DeckSelectionListener, Su
notetypes.change(oldNotetype, noteId, newNotetype, modelChangeFieldMap!!, modelChangeCardMap!!)
}
// refresh the note object to reflect the database changes
withCol { editorNote!!.load() }
withCol { editorNote!!.load(this@withCol) }
// close note editor
closeNoteEditor()
}
@ -1377,7 +1374,7 @@ class NoteEditor : AnkiFragment(R.layout.note_editor), DeckSelectionListener, Su
val tags = selectedTags ?: mutableListOf()
val ord = if (editorNote!!.notetype.isCloze) {
val tempNote = withCol { Note.fromNotetypeId(editorNote!!.notetype.id) }
val tempNote = withCol { Note.fromNotetypeId(this@withCol, editorNote!!.notetype.id) }
tempNote.fields = fields // makes possible to get the cloze numbers from the fields
val clozeNumbers = withCol { clozeNumbersInNote(tempNote) }
if (clozeNumbers.isNotEmpty()) {
@ -1485,7 +1482,12 @@ class NoteEditor : AnkiFragment(R.layout.note_editor), DeckSelectionListener, Su
val tags = ArrayList(getColUnsafe.tags.all())
val selTags = ArrayList(selectedTags!!)
val dialog = with(requireContext()) {
tagsDialogFactory!!.newTagsDialog().withArguments(TagsDialog.DialogType.EDIT_TAGS, selTags, tags)
tagsDialogFactory!!.newTagsDialog().withArguments(
context = this,
type = TagsDialog.DialogType.EDIT_TAGS,
checkedTags = selTags,
allTags = tags
)
}
showDialogFragment(dialog)
}
@ -1544,7 +1546,7 @@ class NoteEditor : AnkiFragment(R.layout.note_editor), DeckSelectionListener, Su
private suspend fun getCurrentMultimediaEditableNote(): MultimediaEditableNote? {
val note = NoteService.createEmptyNote(editorNote!!.notetype)
val fields = currentFieldStrings.requireNoNulls()
withCol { NoteService.updateMultimediaNoteFromFields(fields, editorNote!!.mid, note!!) }
withCol { NoteService.updateMultimediaNoteFromFields(this@withCol, fields, editorNote!!.mid, note!!) }
return note
}
@ -2091,7 +2093,7 @@ class NoteEditor : AnkiFragment(R.layout.note_editor), DeckSelectionListener, Su
editorNote = if (note == null || addNote) {
getColUnsafe.run {
val notetype = notetypes.current()
Note.fromNotetypeId(notetype.id)
Note.fromNotetypeId(this@run, notetype.id)
}
} else {
note

View File

@ -98,7 +98,6 @@ import com.ichi2.libanki.Card
import com.ichi2.libanki.CardId
import com.ichi2.libanki.Collection
import com.ichi2.libanki.Consts
import com.ichi2.libanki.renderOutput
import com.ichi2.libanki.sched.Counts
import com.ichi2.libanki.sched.CurrentQueueState
import com.ichi2.libanki.undoableOp
@ -741,7 +740,7 @@ open class Reviewer :
// Anki Desktop Translations
menu.findItem(R.id.action_reschedule_card).title =
CollectionManager.TR.actionsSetDueDate().toSentenceCase(R.string.sentence_set_due_date)
CollectionManager.TR.actionsSetDueDate().toSentenceCase(this, R.string.sentence_set_due_date)
// Undo button
@DrawableRes val undoIconId: Int
@ -1061,7 +1060,7 @@ open class Reviewer :
override suspend fun updateCurrentCard() {
val state = withCol {
sched.currentQueueState()?.apply {
topCard.renderOutput(true)
topCard.renderOutput(this@withCol, reload = true)
}
}
state?.timeboxReached?.let { dealWithTimeBox(it) }

View File

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

View File

@ -2,12 +2,10 @@
package com.ichi2.anki
import android.app.Activity
import android.content.Context
import android.util.DisplayMetrics
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.fragment.app.Fragment
import com.ichi2.libanki.utils.Time
import java.util.Calendar
@ -23,16 +21,6 @@ fun showThemedToast(context: Context, @StringRes textResource: Int, shortLength:
Toast.makeText(context, textResource, if (shortLength) Toast.LENGTH_SHORT else Toast.LENGTH_LONG).show()
}
context (Fragment)
fun showThemedToast(@StringRes textResource: Int, shortLength: Boolean) {
Toast.makeText(requireContext(), textResource, if (shortLength) Toast.LENGTH_SHORT else Toast.LENGTH_LONG).show()
}
context (Activity)
fun showThemedToast(@StringRes textResource: Int, shortLength: Boolean) {
Toast.makeText(this@Activity, textResource, if (shortLength) Toast.LENGTH_SHORT else Toast.LENGTH_LONG).show()
}
fun getDensityAdjustedValue(context: Context, value: Float): Float {
return context.resources.displayMetrics.density * value
}

View File

@ -52,7 +52,6 @@ import com.ichi2.libanki.Consts.QUEUE_TYPE_MANUALLY_BURIED
import com.ichi2.libanki.Consts.QUEUE_TYPE_SIBLING_BURIED
import com.ichi2.libanki.DeckId
import com.ichi2.libanki.NoteId
import com.ichi2.libanki.hasTag
import com.ichi2.libanki.undoableOp
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
@ -311,7 +310,7 @@ class CardBrowserViewModel(
val initialDeckId = if (selectAllDecks) ALL_DECKS_ID else getInitialDeck()
// PERF: slightly inefficient if the source was lastDeckId
setDeckId(initialDeckId)
val cardsOrNotes = withCol { CardsOrNotes.fromCollection() }
val cardsOrNotes = withCol { CardsOrNotes.fromCollection(this@withCol) }
flowOfCardsOrNotes.update { cardsOrNotes }
val allColumns = withCol { allBrowserColumns() }.associateBy { it.key }
@ -368,7 +367,7 @@ class CardBrowserViewModel(
// if all notes are marked, remove the mark
// if no notes are marked, add the mark
// if there is a mix, enable the mark on all
val wantMark = !noteIds.all { getNote(it).hasTag("marked") }
val wantMark = !noteIds.all { getNote(it).hasTag(this@undoableOp, "marked") }
Timber.i("setting mark = %b for %d notes", wantMark, noteIds.size)
if (wantMark) {
tags.bulkAdd(noteIds, "marked")
@ -399,7 +398,7 @@ class CardBrowserViewModel(
Timber.i("setting mode to %s", newValue)
withCol {
// Change this to only change the preference on a state change
newValue.saveToCollection()
newValue.saveToCollection(this@withCol)
}
flowOfCardsOrNotes.update { newValue }
setupColumns(newValue)

View File

@ -144,7 +144,7 @@ class CardMediaPlayer : Closeable {
this.answers = renderOutput.answerAvTags
if (!this::config.isInitialized || !config.appliesTo(card)) {
config = withCol { CardSoundConfig.create(card) }
config = withCol { CardSoundConfig.create(this@withCol, card) }
}
}
@ -162,7 +162,7 @@ class CardMediaPlayer : Closeable {
this.answers = renderOutput.answerAvTags
if (!this::config.isInitialized || !config.appliesTo(card)) {
config = withCol { CardSoundConfig.create(card) }
config = withCol { CardSoundConfig.create(this@withCol, card) }
}
}

View File

@ -37,14 +37,13 @@ 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(card: Card): CardSoundConfig {
fun create(col: Collection, card: Card): CardSoundConfig {
Timber.v("start loading SoundConfig")
val autoPlay = card.autoplay(this@Collection)
val autoPlay = card.autoplay(col)
val replayQuestion: Boolean = card.replayQuestionAudioOnAnswerSide(this@Collection)
val replayQuestion: Boolean = card.replayQuestionAudioOnAnswerSide(col)
return CardSoundConfig(replayQuestion, autoPlay, card.did).apply {
Timber.d("loaded SoundConfig: %s", this)

View File

@ -66,10 +66,9 @@ object BackgroundImage {
data class UncompressedBitmapTooLarge(val width: Long, val height: Long) : FileSizeResult
}
context (AppearanceSettingsFragment)
fun validateBackgroundImageFileSize(selectedImage: Uri): FileSizeResult {
fun validateBackgroundImageFileSize(target: AppearanceSettingsFragment, selectedImage: Uri): FileSizeResult {
val filePathColumn = arrayOf(MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.WIDTH, MediaStore.MediaColumns.HEIGHT)
requireContext().contentResolver.query(selectedImage, filePathColumn, null, null, null).use { cursor ->
target.requireContext().contentResolver.query(selectedImage, filePathColumn, null, null, null).use { cursor ->
cursor!!.moveToFirst()
val fileSizeInMB = cursor.getLong(0) / (1024 * 1024)
if (fileSizeInMB >= 10) {
@ -88,15 +87,14 @@ object BackgroundImage {
}
}
context (AppearanceSettingsFragment)
fun import(selectedImage: Uri) {
val currentAnkiDroidDirectory = CollectionHelper.getCurrentAnkiDroidDirectory(requireContext())
fun import(target: AppearanceSettingsFragment, selectedImage: Uri) {
val currentAnkiDroidDirectory = CollectionHelper.getCurrentAnkiDroidDirectory(target.requireContext())
val imageName = "DeckPickerBackground.png"
val destFile = File(currentAnkiDroidDirectory, imageName)
(requireContext().contentResolver.openInputStream(selectedImage) as FileInputStream).channel.use { sourceChannel ->
(target.requireContext().contentResolver.openInputStream(selectedImage) as FileInputStream).channel.use { sourceChannel ->
FileOutputStream(destFile).channel.use { destChannel ->
destChannel.transferFrom(sourceChannel, 0, sourceChannel.size())
showSnackbar(R.string.background_image_applied)
target.showSnackbar(R.string.background_image_applied)
}
}
this.enabled = true

View File

@ -149,13 +149,12 @@ class CustomStudyDialog(private val collection: Collection, private val customSt
*/
val currentDeck = requireArguments().getLong("did")
val dialogFragment = with(requireContext()) {
TagsDialog().withArguments(
TagsDialog.DialogType.CUSTOM_STUDY_TAGS,
ArrayList(),
ArrayList(collection.tags.byDeck(currentDeck))
)
}
val dialogFragment = TagsDialog().withArguments(
context = requireContext(),
type = TagsDialog.DialogType.CUSTOM_STUDY_TAGS,
checkedTags = ArrayList(),
allTags = ArrayList(collection.tags.byDeck(currentDeck))
)
customStudyListener?.showDialogFragment(dialogFragment)
}
else -> {

View File

@ -107,9 +107,8 @@ class TagsDialog : AnalyticsDialogFragment {
* @param allTags all possible tags in the collection
* @return Initialized instance of [TagsDialog]
*/
context(Context)
fun withArguments(type: DialogType, checkedTags: List<String>, allTags: List<String>): TagsDialog {
return withArguments(type, checkedTags, null, allTags)
fun withArguments(context: Context, type: DialogType, checkedTags: List<String>, allTags: List<String>): TagsDialog {
return withArguments(context, type, checkedTags, null, allTags)
}
/**
@ -121,15 +120,15 @@ class TagsDialog : AnalyticsDialogFragment {
* @param allTags all possible tags in the collection
* @return Initialized instance of [TagsDialog]
*/
context(Context)
fun withArguments(
context: Context,
type: DialogType,
checkedTags: List<String>,
uncheckedTags: List<String>?,
allTags: List<String>
): TagsDialog {
val data = TagsFile.TagsData(type, checkedTags, uncheckedTags, allTags)
val file = TagsFile(cacheDir, data)
val file = TagsFile(context.cacheDir, data)
arguments = this.arguments ?: bundleOf(
ARG_TAGS_FILE to file
)

View File

@ -121,7 +121,7 @@ class InstantEditorViewModel : ViewModel(), OnErrorListener {
Timber.d("Changing to cloze type note")
_currentlySelectedNotetype.postValue(clozeNoteType)
Timber.i("Using note type '%d", clozeNoteType.id)
editorNote = withCol { Note.fromNotetypeId(clozeNoteType.id) }
editorNote = withCol { Note.fromNotetypeId(this@withCol, clozeNoteType.id) }
_dialogType.emit(DialogType.SHOW_EDITOR_DIALOG)
}

View File

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

View File

@ -169,7 +169,7 @@ class AppearanceSettingsFragment : SettingsFragment() {
}
// handling file may result in exception
try {
when (val sizeResult = BackgroundImage.validateBackgroundImageFileSize(selectedImage)) {
when (val sizeResult = BackgroundImage.validateBackgroundImageFileSize(this, selectedImage)) {
is FileSizeResult.FileTooLarge -> {
showThemedToast(requireContext(), getString(R.string.image_max_size_allowed, sizeResult.maxMB), false)
}
@ -177,7 +177,7 @@ class AppearanceSettingsFragment : SettingsFragment() {
showThemedToast(requireContext(), getString(R.string.image_dimensions_too_large, sizeResult.width, sizeResult.height), false)
}
is FileSizeResult.OK -> {
BackgroundImage.import(selectedImage)
BackgroundImage.import(this, selectedImage)
}
}
} catch (e: OutOfMemoryError) {

View File

@ -15,6 +15,7 @@
*/
package com.ichi2.anki.preferences
import androidx.annotation.StringRes
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.R
import com.ichi2.anki.cardviewer.ViewerCommand
@ -77,4 +78,7 @@ class ControlsSettingsFragment : SettingsFragment() {
it.title = getString(R.string.gesture_flag_remove).toSentenceCase(R.string.sentence_gesture_flag_remove)
}
}
private fun String.toSentenceCase(@StringRes resId: Int): String =
this.toSentenceCase(this@ControlsSettingsFragment, resId)
}

View File

@ -60,7 +60,7 @@ class CustomButtonsSettingsFragment : SettingsFragment() {
private fun setDynamicTitle() {
findPreference<Preference>(getString(R.string.custom_button_schedule_card_key))?.let {
it.title = TR.actionsSetDueDate().toSentenceCase(R.string.sentence_set_due_date)
it.title = TR.actionsSetDueDate().toSentenceCase(this, R.string.sentence_set_due_date)
}
}

View File

@ -34,7 +34,6 @@ import com.ichi2.anki.pages.PostRequestHandler
import com.ichi2.libanki.Card
import com.ichi2.libanki.Sound
import com.ichi2.libanki.TtsPlayer
import com.ichi2.libanki.note
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@ -223,7 +222,7 @@ abstract class CardViewerViewModel(
suspend fun getExpectedTypeInAnswer(card: Card, field: JSONObject): String? {
val fieldName = field.getString("name")
val expected = withCol { card.note().getItem(fieldName) }
val expected = withCol { card.note(this@withCol).getItem(fieldName) }
return if (fieldName.startsWith("cloze:")) {
val clozeIdx = card.ord + 1
withCol {

View File

@ -35,8 +35,6 @@ import com.ichi2.anki.servicelayer.MARKED_TAG
import com.ichi2.anki.servicelayer.NoteService
import com.ichi2.annotations.NeedsTest
import com.ichi2.libanki.Card
import com.ichi2.libanki.hasTag
import com.ichi2.libanki.note
import com.ichi2.libanki.undoableOp
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.flow.MutableStateFlow
@ -116,7 +114,7 @@ class PreviewerViewModel(previewerIdsFile: PreviewerIdsFile, firstIndex: Int, ca
fun toggleMark() {
launchCatchingIO {
val card = currentCard.await()
val note = withCol { card.note() }
val note = withCol { card.note(this@withCol) }
NoteService.toggleMark(note)
isMarked.emit(NoteService.isMarked(note))
}
@ -217,7 +215,7 @@ class PreviewerViewModel(previewerIdsFile: PreviewerIdsFile, firstIndex: Int, ca
private suspend fun updateMarkIcon() {
val card = currentCard.await()
val isMarkedValue = withCol { card.note().hasTag(MARKED_TAG) }
val isMarkedValue = withCol { card.note(this@withCol).hasTag(this@withCol, MARKED_TAG) }
isMarked.emit(isMarkedValue)
}

View File

@ -68,7 +68,7 @@ class TemplatePreviewerViewModel(
if (arguments.id != 0L) {
Note(this, arguments.id)
} else {
Note.fromNotetypeId(arguments.notetype.id)
Note.fromNotetypeId(this@withCol, arguments.notetype.id)
}
}.apply {
fields = arguments.fields

View File

@ -723,7 +723,7 @@ class CardContentProvider : ContentProvider() {
}
val fldsArray = Utils.splitFields(flds)
// Create empty note
val newNote = col.run { Note.fromNotetypeId(thisModelId) }
val newNote = Note.fromNotetypeId(col, thisModelId)
// Set fields
// Check that correct number of flds specified
if (fldsArray.size != newNote.fields.size) {
@ -766,7 +766,7 @@ class CardContentProvider : ContentProvider() {
val tags = values.getAsString(FlashCardsContract.Note.TAGS)
// val allowEmpty = AllowEmpty.fromBoolean(values.getAsBoolean(FlashCardsContract.Note.ALLOW_EMPTY))
// Create empty note
val newNote = col.run { Note.fromNotetypeId(modelId) }
val newNote = Note.fromNotetypeId(col, modelId)
// Set fields
val fldsArray = Utils.splitFields(flds)
// Check that correct number of flds specified

View File

@ -27,8 +27,6 @@ import com.ichi2.anki.CollectionManager.withCol
import com.ichi2.anki.R
import com.ichi2.libanki.Card
import com.ichi2.libanki.Collection
import com.ichi2.libanki.timeLimit
import com.ichi2.libanki.timeTaken
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -124,10 +122,10 @@ class AnswerTimer(private val cardTimer: Chronometer) {
}
// Then update and resume the UI timer. Set the base time as if the timer had started
// timeTaken() seconds ago.
setBase(elapsedRealTime - withCol { currentCard.timeTaken() })
setBase(elapsedRealTime - withCol { currentCard.timeTaken(this@withCol) })
// Don't start the timer if we have already reached the time limit or it will tick over
if (elapsedRealTime - cardTimer.base < withCol { currentCard.timeLimit() }) {
if (elapsedRealTime - cardTimer.base < withCol { currentCard.timeLimit(this@withCol) }) {
cardTimer.start()
}
}

View File

@ -109,7 +109,7 @@ class ForgetCardsDialog : DialogFragment() {
}
// this can outlive the lifetime of the fragment
private fun AnkiActivity.forgetCards(viewModel: ForgetCardsViewModel) = this@AnkiActivity.launchCatchingTask {
private fun AnkiActivity.forgetCards(viewModel: ForgetCardsViewModel) = this.launchCatchingTask {
// NICE_TO_HAVE: Display a snackbar if the activity is recreated while this executes
val count = withProgress(resources.getString(R.string.dialog_processing)) {
// this should be run on the viewModel

View File

@ -17,6 +17,7 @@
package com.ichi2.anki.scheduling
import android.app.Dialog
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.view.View
@ -108,7 +109,7 @@ class SetDueDateDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext()).create {
title(text = CollectionManager.TR.actionsSetDueDate().toSentenceCase(R.string.sentence_set_due_date))
title(text = CollectionManager.TR.actionsSetDueDate().toSentenceCase(this@SetDueDateDialog, R.string.sentence_set_due_date))
positiveButton(R.string.dialog_ok) { launchUpdateDueDate() }
negativeButton(R.string.dialog_cancel)
neutralButton(R.string.help)
@ -163,7 +164,7 @@ class SetDueDateDialog : DialogFragment() {
// The dialog is too wide on tablets
// Select either 450dp (tablets)
// or 100% of the screen width (smaller phones)
val intendedWidth = min(MAX_WIDTH_DP.dpToPx, resources.displayMetrics.widthPixels)
val intendedWidth = min(MAX_WIDTH_DP.dpToPx(this.requireContext()), resources.displayMetrics.widthPixels)
Timber.d("updating width to %d", intendedWidth)
this.dialog?.window?.setLayout(
intendedWidth,
@ -275,7 +276,7 @@ class SetDueDateDialog : DialogFragment() {
}
// this can outlive the lifetime of the fragment
private fun AnkiActivity.updateDueDate(viewModel: SetDueDateViewModel) = this@AnkiActivity.launchCatchingTask {
private fun AnkiActivity.updateDueDate(viewModel: SetDueDateViewModel) = this.launchCatchingTask {
// NICE_TO_HAVE: Display a snackbar if the activity is recreated while this executes
val cardsUpdated = withProgress(resources.getString(R.string.dialog_processing)) {
// this is async as it should be run on the viewModel
@ -285,12 +286,12 @@ private fun AnkiActivity.updateDueDate(viewModel: SetDueDateViewModel) = this@An
if (cardsUpdated == null) {
Timber.w("unable to update due date")
showThemedToast(R.string.something_wrong, true)
showThemedToast(this@updateDueDate, R.string.something_wrong, true)
return@launchCatchingTask
}
showSnackbar(TR.schedulingSetDueDateDone(cardsUpdated), Snackbar.LENGTH_SHORT)
}
context (DialogFragment)
val Int.dpToPx: Int
get() = (this * requireContext().resources.displayMetrics.density + 0.5f).toInt()
// TODO: See if we can turn this to a `val` when context parameters are back
fun Int.dpToPx(context: Context): Int =
(this * context.resources.displayMetrics.density + 0.5f).toInt()

View File

@ -26,10 +26,9 @@ object CardService {
* @param selectedCardIds list of card ids
* can do better with performance here
*/
context (Collection)
fun selectedNoteIds(selectedCardIds: List<CardId>) =
fun selectedNoteIds(col: Collection, selectedCardIds: List<CardId>) =
CardUtils.getNotes(
this@Collection,
selectedCardIds.map { this@Collection.getCard(it) }
col,
selectedCardIds.map { col.getCard(it) }
).map { it.id }
}

View File

@ -78,8 +78,12 @@ object NoteService {
return null
}
context (Collection)
fun updateMultimediaNoteFromFields(fields: Array<String>, modelId: NoteTypeId, mmNote: MultimediaEditableNote) {
fun updateMultimediaNoteFromFields(
col: Collection,
fields: Array<String>,
modelId: NoteTypeId,
mmNote: MultimediaEditableNote
) {
for (i in fields.indices) {
val value = fields[i]
val field: IField = if (value.startsWith("<img")) {
@ -91,7 +95,7 @@ object NoteService {
} else {
TextField()
}
field.setFormattedString(this@Collection, value)
field.setFormattedString(col, value)
mmNote.setField(i, field)
}
mmNote.modelId = modelId
@ -118,10 +122,6 @@ object NoteService {
}
}
context (Collection)
fun importMediaFileToDirectory(field: IField?) =
importMediaToDirectory(this@Collection, field)
/**
* Considering the field is new, if it has media handle it
*

View File

@ -29,17 +29,15 @@ import androidx.fragment.app.Fragment
* "Toggle Suspend".toSentenceCase(R.string.sentence_toggle_suspend) // "Toggle suspend"
* ```
*/
context (Activity)
fun String.toSentenceCase(@StringRes resId: Int): String {
val resString = getString(resId)
fun String.toSentenceCase(activity: Activity, @StringRes resId: Int): String {
val resString = activity.getString(resId)
// lowercase both for the comparison: sentence case doesn't mean all words are lowercase
if (this.lowercase() == resString.lowercase()) return resString
return this
}
context (Fragment)
fun String.toSentenceCase(@StringRes resId: Int): String {
val resString = getString(resId)
fun String.toSentenceCase(fragment: Fragment, @StringRes resId: Int): String {
val resString = fragment.getString(resId)
// lowercase both for the comparison: sentence case doesn't mean all words are lowercase
if (this.lowercase() == resString.lowercase()) return resString
return this

View File

@ -95,7 +95,7 @@ class ManageSpaceViewModel(val app: Application) : AndroidViewModel(app), Collec
suspend fun deleteMediaFiles(filesNamesToDelete: List<String>) {
try {
withCol { deleteMedia(filesNamesToDelete) }
withCol { deleteMedia(this@withCol, filesNamesToDelete) }
} finally {
launchCalculationOfSizeOfEverything()
launchCalculationOfCollectionSize()
@ -117,7 +117,7 @@ class ManageSpaceViewModel(val app: Application) : AndroidViewModel(app), Collec
suspend fun deleteBackups(backupsToDelete: List<File>) {
try {
withCol { BackupManager.deleteBackups(backupsToDelete) }
withCol { BackupManager.deleteBackups(this@withCol, backupsToDelete) }
} finally {
launchCalculationOfBackupsSize()
launchCalculationOfCollectionSize()

View File

@ -43,8 +43,6 @@ import com.ichi2.anki.servicelayer.isBuryNoteAvailable
import com.ichi2.anki.servicelayer.isSuspendNoteAvailable
import com.ichi2.anki.ui.windows.reviewer.autoadvance.AutoAdvance
import com.ichi2.libanki.ChangeManager
import com.ichi2.libanki.hasTag
import com.ichi2.libanki.note
import com.ichi2.libanki.redo
import com.ichi2.libanki.sched.Counts
import com.ichi2.libanki.sched.CurrentQueueState
@ -161,7 +159,7 @@ class ReviewerViewModel(cardMediaPlayer: CardMediaPlayer) :
fun toggleMark() {
launchCatchingIO {
val card = currentCard.await()
val note = withCol { card.note() }
val note = withCol { card.note(this@withCol) }
NoteService.toggleMark(note)
isMarkedFlow.emit(NoteService.isMarked(note))
}
@ -371,7 +369,7 @@ class ReviewerViewModel(cardMediaPlayer: CardMediaPlayer) :
private suspend fun updateMarkedStatus() {
val card = currentCard.await()
val isMarkedValue = withCol { card.note().hasTag(MARKED_TAG) }
val isMarkedValue = withCol { card.note(this@withCol).hasTag(this@withCol, MARKED_TAG) }
isMarkedFlow.emit(isMarkedValue)
}

View File

@ -32,20 +32,18 @@ import timber.log.Timber
* Takes a list of media file names and removes them from the [Collection]
* @param unused List of media names to be deleted
*/
context (Collection)
fun deleteMedia(unused: List<String>): Int {
fun deleteMedia(col: Collection, unused: List<String>): Int {
// FIXME: this provides progress info that is not currently used
this@Collection.media.removeFiles(unused)
col.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]
context (Collection)
fun updateValuesFromDeck(): StudyOptionsFragment.DeckStudyData? {
fun updateValuesFromDeck(col: Collection): StudyOptionsFragment.DeckStudyData? {
Timber.d("doInBackgroundUpdateValuesFromDeck")
return try {
val sched = this@Collection.sched
val sched = col.sched
val counts = sched.counts()
val totalNewCount = sched.totalNewForCurrentDeck()
val totalCount = sched.cardCount()
@ -117,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 = this@Collection.notetypes.get(notetype.getLong("id"))
val oldModel = col.notetypes.get(notetype.getLong("id"))
// TODO: make undoable
val newTemplates = notetype.getJSONArray("tmpls")
@ -132,11 +130,11 @@ fun saveModel(
when (change[1] as CardTemplateNotetype.ChangeType) {
CardTemplateNotetype.ChangeType.ADD -> {
Timber.d("doInBackgroundSaveModel() adding template %s", change[0])
this@Collection.notetypes.addTemplate(oldModel, newTemplates.getJSONObject(change[0] as Int))
col.notetypes.addTemplate(oldModel, newTemplates.getJSONObject(change[0] as Int))
}
CardTemplateNotetype.ChangeType.DELETE -> {
Timber.d("doInBackgroundSaveModel() deleting template currently at ordinal %s", change[0])
this@Collection.notetypes.remTemplate(oldModel, oldTemplates.getJSONObject(change[0] as Int))
col.notetypes.remTemplate(oldModel, oldTemplates.getJSONObject(change[0] as Int))
}
}
}
@ -144,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"))
this@Collection.notetypes.save(notetype)
this@Collection.notetypes.update(notetype)
col.notetypes.save(notetype)
col.notetypes.update(notetype)
}

View File

@ -229,6 +229,3 @@ interface Compat {
@Suppress("PropertyName")
val AXIS_GESTURE_SCROLL_Y_DISTANCE: Int
}
context (Context)
fun Compat.vibrate(duration: Duration) = vibrate(this@Context, duration)

View File

@ -463,23 +463,3 @@ 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)
/** @see Card.timeTaken */
context (Collection)
fun Card.timeTaken() =
this@Card.timeTaken(this@Collection)
/** @see Card.timeLimit */
context (Collection)
fun Card.timeLimit() =
this@Card.timeLimit(this@Collection)

View File

@ -310,7 +310,7 @@ class Collection(
* @return The new note
*/
fun newNote(notetype: NotetypeJson): Note {
return Note.fromNotetypeId(notetype.id)
return Note.fromNotetypeId(this, notetype.id)
}
/**

View File

@ -65,10 +65,9 @@ class Note : Cloneable {
}
companion object {
context (Collection)
fun fromNotetypeId(ntid: NoteTypeId): Note {
val backendNote = backend.newNote(ntid)
return Note(this@Collection, backendNote)
fun fromNotetypeId(col: Collection, ntid: NoteTypeId): Note {
val backendNote = col.backend.newNote(ntid)
return Note(col, backendNote)
}
}
@ -301,15 +300,3 @@ class Note : Cloneable {
}
}
}
/** @see Note.hasTag */
context (Collection)
fun Note.hasTag(tag: String) = this.hasTag(this@Collection, tag)
/** @see Note.setTagsFromStr */
context (Collection)
fun Note.setTagsFromStr(str: String) = this.setTagsFromStr(this@Collection, str)
/** @see Note.load */
context (Collection)
fun Note.load() = this.load(this@Collection)

View File

@ -63,13 +63,13 @@ class CardBrowserColumnTest : JvmTest() {
@Test
fun `cards - ensure old values match backend values`() {
with(col) { CardsOrNotes.CARDS.saveToCollection() }
CardsOrNotes.CARDS.saveToCollection(col)
`ensure old values match backend values`(CardsOrNotes.CARDS)
}
@Test
fun `notes - ensure old values match backend values`() {
with(col) { CardsOrNotes.NOTES.saveToCollection() }
CardsOrNotes.NOTES.saveToCollection(col)
`ensure old values match backend values`(CardsOrNotes.NOTES)
}

View File

@ -247,7 +247,7 @@ class CardBrowserViewModelTest : JvmTest() {
fun `sort order from notes is selected - 16514`() {
col.config.set("sortType", "noteCrt")
col.config.set("noteSortType", "_field_Frequency")
with(col) { CardsOrNotes.NOTES.saveToCollection() }
CardsOrNotes.NOTES.saveToCollection(col)
runViewModelTest(notes = 1) {
assertThat("1 row returned", rowCount, equalTo(1))
@ -716,7 +716,7 @@ class CardBrowserViewModelTest : JvmTest() {
testBody: suspend CardBrowserViewModel.() -> Unit
) =
runTest {
with(col) { CardsOrNotes.NOTES.saveToCollection() }
CardsOrNotes.NOTES.saveToCollection(col)
for (i in 0 until notes) {
// ensure 1 note = 2 cards
addNoteUsingBasicAndReversedModel()
@ -770,7 +770,7 @@ class CardBrowserViewModelTest : JvmTest() {
// default is CARDS, do nothing in this case
if (mode == CardsOrNotes.NOTES) {
CollectionManager.withCol { mode.saveToCollection() }
CollectionManager.withCol { mode.saveToCollection(this@withCol) }
}
val cache = File(createTempDirectory().pathString)

View File

@ -68,5 +68,5 @@ class CardSoundConfigTest : JvmTest() {
fun `cards with the same deck options are equal`() {
}
private suspend fun createCardSoundConfig(card: Card) = withCol { CardSoundConfig.create(card) }
private suspend fun createCardSoundConfig(card: Card) = withCol { CardSoundConfig.create(this@withCol, card) }
}

View File

@ -688,25 +688,25 @@ class TagsDialogTest : RobolectricTest() {
type: TagsDialog.DialogType,
checkedTags: List<String>,
allTags: List<String>
) =
with(this@TagsDialogTest.targetContext) {
withArguments(type = type, checkedTags = checkedTags, allTags = allTags)
}
) = withArguments(
context = targetContext,
type = type,
checkedTags = checkedTags,
allTags = allTags
)
private fun TagsDialog.withTestArguments(
type: TagsDialog.DialogType,
checkedTags: List<String>,
uncheckedTags: List<String>?,
allTags: List<String>
) =
with(this@TagsDialogTest.targetContext) {
withArguments(
type = type,
checkedTags = checkedTags,
uncheckedTags = uncheckedTags,
allTags = allTags
)
}
) = withArguments(
context = targetContext,
type = type,
checkedTags = checkedTags,
uncheckedTags = uncheckedTags,
allTags = allTags
)
companion object {
private fun mockLifecycleOwner(): LifecycleOwner {

View File

@ -216,8 +216,7 @@ class InstantEditorViewModelTest : RobolectricTest() {
}
companion object {
context (TestClass)
fun runInstantEditorViewModelTest(
fun TestClass.runInstantEditorViewModelTest(
initViewModel: () -> InstantEditorViewModel = { InstantEditorViewModel() },
testBody: suspend InstantEditorViewModel.() -> Unit
) = runTest {
@ -227,23 +226,20 @@ class InstantEditorViewModelTest : RobolectricTest() {
}
}
context (InstantEditorViewModel)
private fun toggleAllClozeDeletions(words: MutableList<String>) {
private fun InstantEditorViewModel.toggleAllClozeDeletions(words: MutableList<String>) {
for (index in words.indices) {
words[index] = buildClozeText(words[index])
}
}
context (InstantEditorViewModel)
@Suppress("SameParameterValue")
private fun toggleClozeDeletions(words: MutableList<String>, vararg indices: Int) {
private fun InstantEditorViewModel.toggleClozeDeletions(words: MutableList<String>, vararg indices: Int) {
for (index in indices) {
words[index] = buildClozeText(words[index])
}
}
context (InstantEditorViewModel)
@Suppress("SameParameterValue")
private fun toggleClozeDeletion(words: MutableList<String>, index: Int) {
private fun InstantEditorViewModel.toggleClozeDeletion(words: MutableList<String>, index: Int) {
words[index] = buildClozeText(words[index])
}

View File

@ -60,7 +60,7 @@ class NoteServiceTest : RobolectricTest() {
multiMediaNote!!.getField(0)!!.text = "foo"
multiMediaNote.getField(1)!!.text = "bar"
val basicNote = col.run { Note.fromNotetypeId(testModel.id) }.apply {
val basicNote = Note.fromNotetypeId(col, testModel.id).apply {
setField(0, "this should be changed to foo")
setField(1, "this should be changed to bar")
}
@ -81,7 +81,7 @@ class NoteServiceTest : RobolectricTest() {
testNotetype = col.notetypes.newBasicNotetype()
testNotetype.id = 45
col.notetypes.add(testNotetype)
val noteWithID45 = col.run { Note.fromNotetypeId(testNotetype.id) }
val noteWithID45 = Note.fromNotetypeId(col, testNotetype.id)
val expectedException: Throwable = assertThrows(RuntimeException::class.java) { NoteService.updateJsonNoteFromMultimediaNote(multiMediaNoteWithID42, noteWithID45) }
assertEquals(expectedException.message, "Source and Destination Note ID do not match.")
}

View File

@ -34,12 +34,12 @@ class SentenceCaseTest : RobolectricTest() {
@Test
fun `English is converted to sentence case`() {
with(super.startRegularActivity<IntroductionActivity>()) {
assertThat(TR.browsingToggleSuspend().toSentenceCase(R.string.sentence_toggle_suspend), equalTo("Toggle suspend"))
assertThat(TR.browsingToggleBury().toSentenceCase(R.string.sentence_toggle_bury), equalTo("Toggle bury"))
assertThat(TR.actionsSetDueDate().toSentenceCase(R.string.sentence_set_due_date), equalTo("Set due date"))
assertThat(TR.browsingToggleSuspend().toSentenceCase(this, R.string.sentence_toggle_suspend), equalTo("Toggle suspend"))
assertThat(TR.browsingToggleBury().toSentenceCase(this, R.string.sentence_toggle_bury), equalTo("Toggle bury"))
assertThat(TR.actionsSetDueDate().toSentenceCase(this, R.string.sentence_set_due_date), equalTo("Set due date"))
assertThat("Toggle Suspend".toSentenceCase(R.string.sentence_toggle_suspend), equalTo("Toggle suspend"))
assertThat("Ook? Ook?".toSentenceCase(R.string.sentence_toggle_suspend), equalTo("Ook? Ook?"))
assertThat("Toggle Suspend".toSentenceCase(this, R.string.sentence_toggle_suspend), equalTo("Toggle suspend"))
assertThat("Ook? Ook?".toSentenceCase(this, R.string.sentence_toggle_suspend), equalTo("Ook? Ook?"))
}
}
}

View File

@ -63,8 +63,7 @@ class OnlyOnceTest : RobolectricTest() {
}
// catch the exception here otherwise the test scope will catch it and throw it, safe as we expect the exception
context(TestScope)
private fun preventMultipleExecutions(
private fun TestScope.preventMultipleExecutions(
shouldCatchException: Boolean = false,
wait: Boolean,
function: () -> Unit
@ -78,6 +77,6 @@ class OnlyOnceTest : RobolectricTest() {
}
}
}
if (wait) this@TestScope.advanceUntilIdle()
if (wait) advanceUntilIdle()
}
}

View File

@ -83,7 +83,7 @@ subprojects {
tasks.withType(KotlinCompile::class.java).configureEach {
compilerOptions {
allWarningsAsErrors = fatalWarnings
val compilerArgs = mutableListOf("-Xjvm-default=all", "-Xcontext-receivers")
val compilerArgs = mutableListOf("-Xjvm-default=all")
if (project.name != "api") {
compilerArgs += "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
}