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:
parent
da41ac1cb1
commit
672b075a54
@ -432,7 +432,7 @@ abstract class AbstractFlashcardViewer :
|
|||||||
val card = editorCard!!
|
val card = editorCard!!
|
||||||
withProgress {
|
withProgress {
|
||||||
undoableOp {
|
undoableOp {
|
||||||
updateNote(card.note(this))
|
updateNote(card.note())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onCardUpdated(card)
|
onCardUpdated(card)
|
||||||
@ -477,7 +477,7 @@ abstract class AbstractFlashcardViewer :
|
|||||||
// despite that making no sense outside of Reviewer.kt
|
// despite that making no sense outside of Reviewer.kt
|
||||||
currentCard = withCol {
|
currentCard = withCol {
|
||||||
sched.card?.apply {
|
sched.card?.apply {
|
||||||
renderOutput(this@withCol)
|
renderOutput()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
* 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
|
* 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
|
* @return whether the repair was successful
|
||||||
*/
|
*/
|
||||||
fun repairCollection(col: Collection): Boolean {
|
context (Collection)
|
||||||
val colPath = col.path
|
fun repairCollection(): Boolean {
|
||||||
|
val colPath = this@Collection.path
|
||||||
val colFile = File(colPath)
|
val colFile = File(colPath)
|
||||||
val time = TimeManager.time
|
val time = TimeManager.time
|
||||||
Timber.i("BackupManager - RepairCollection - Closing Collection")
|
Timber.i("BackupManager - RepairCollection - Closing Collection")
|
||||||
col.close()
|
this@Collection.close()
|
||||||
|
|
||||||
// repair file
|
// repair file
|
||||||
val execString = "sqlite3 $colPath .dump | sqlite3 $colPath.tmp"
|
val execString = "sqlite3 $colPath .dump | sqlite3 $colPath.tmp"
|
||||||
@ -457,9 +457,10 @@ open class BackupManager {
|
|||||||
*
|
*
|
||||||
* @return Whether all specified backups were successfully deleted.
|
* @return Whether all specified backups were successfully deleted.
|
||||||
*/
|
*/
|
||||||
|
context (Collection)
|
||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
fun deleteBackups(collection: Collection, backupsToDelete: List<File>): Boolean {
|
fun deleteBackups(backupsToDelete: List<File>): Boolean {
|
||||||
val allBackups = getBackups(File(collection.path))
|
val allBackups = getBackups(File(this@Collection.path))
|
||||||
val invalidBackupsToDelete = backupsToDelete.toSet() - allBackups.toSet()
|
val invalidBackupsToDelete = backupsToDelete.toSet() - allBackups.toSet()
|
||||||
|
|
||||||
if (invalidBackupsToDelete.isNotEmpty()) {
|
if (invalidBackupsToDelete.isNotEmpty()) {
|
||||||
|
@ -89,6 +89,7 @@ import com.ichi2.anki.widgets.DeckDropDownAdapter.SubtitleListener
|
|||||||
import com.ichi2.annotations.NeedsTest
|
import com.ichi2.annotations.NeedsTest
|
||||||
import com.ichi2.async.*
|
import com.ichi2.async.*
|
||||||
import com.ichi2.libanki.*
|
import com.ichi2.libanki.*
|
||||||
|
import com.ichi2.libanki.Collection
|
||||||
import com.ichi2.ui.CardBrowserSearchView
|
import com.ichi2.ui.CardBrowserSearchView
|
||||||
import com.ichi2.ui.FixedTextView
|
import com.ichi2.ui.FixedTextView
|
||||||
import com.ichi2.utils.*
|
import com.ichi2.utils.*
|
||||||
@ -515,7 +516,7 @@ open class CardBrowser :
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finish initializing the activity after the collection has been correctly loaded
|
// 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)
|
super.onCollectionLoaded(col)
|
||||||
Timber.d("onCollectionLoaded()")
|
Timber.d("onCollectionLoaded()")
|
||||||
registerExternalStorageListener()
|
registerExternalStorageListener()
|
||||||
@ -1523,7 +1524,7 @@ open class CardBrowser :
|
|||||||
private suspend fun editSelectedCardsTags(selectedTags: List<String>, indeterminateTags: List<String>) = withProgress {
|
private suspend fun editSelectedCardsTags(selectedTags: List<String>, indeterminateTags: List<String>) = withProgress {
|
||||||
undoableOp {
|
undoableOp {
|
||||||
val selectedNotes = selectedCardIds
|
val selectedNotes = selectedCardIds
|
||||||
.map { cardId -> getCard(cardId).note(this) }
|
.map { cardId -> getCard(cardId).note() }
|
||||||
.distinct()
|
.distinct()
|
||||||
.onEach { note ->
|
.onEach { note ->
|
||||||
val previousTags: List<String> = note.tags
|
val previousTags: List<String> = note.tags
|
||||||
@ -1563,7 +1564,7 @@ open class CardBrowser :
|
|||||||
val card = cardBrowserCard!!
|
val card = cardBrowserCard!!
|
||||||
withProgress {
|
withProgress {
|
||||||
undoableOp {
|
undoableOp {
|
||||||
updateNote(card.note(this))
|
updateNote(card.note())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateCardInList(card)
|
updateCardInList(card)
|
||||||
@ -1573,7 +1574,7 @@ open class CardBrowser :
|
|||||||
* Removes cards from view. Doesn't delete them in model (database).
|
* 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)
|
* @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 idToPos = viewModel.cardIdToPositionMap
|
||||||
val idToRemove = cardsIds.filter { cId -> idToPos.containsKey(cId) }
|
val idToRemove = cardsIds.filter { cId -> idToPos.containsKey(cId) }
|
||||||
mReloadRequired = mReloadRequired || cardsIds.contains(reviewerCardId)
|
mReloadRequired = mReloadRequired || cardsIds.contains(reviewerCardId)
|
||||||
@ -1940,7 +1941,7 @@ open class CardBrowser :
|
|||||||
override var position: Int
|
override var position: Int
|
||||||
|
|
||||||
private val inCardMode: Boolean
|
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.position = position
|
||||||
this.inCardMode = cardsOrNotes == CARDS
|
this.inCardMode = cardsOrNotes == CARDS
|
||||||
}
|
}
|
||||||
@ -2259,13 +2260,14 @@ suspend fun searchForCards(
|
|||||||
): MutableList<CardBrowser.CardCache> {
|
): MutableList<CardBrowser.CardCache> {
|
||||||
return withCol {
|
return withCol {
|
||||||
(if (cardsOrNotes == CARDS) findCards(query, order) else findOneCardByNote(query, order)).asSequence()
|
(if (cardsOrNotes == CARDS) findCards(query, order) else findOneCardByNote(query, order)).asSequence()
|
||||||
.toCardCache(this, cardsOrNotes)
|
.toCardCache(cardsOrNotes)
|
||||||
.toMutableList()
|
.toMutableList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Sequence<CardId>.toCardCache(col: com.ichi2.libanki.Collection, isInCardMode: CardsOrNotes): Sequence<CardBrowser.CardCache> {
|
context (Collection)
|
||||||
return this.mapIndexed { idx, cid -> CardBrowser.CardCache(cid, col, idx, isInCardMode) }
|
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)
|
class Previewer2Destination(val currentIndex: Int, val selectedCardIds: LongArray)
|
||||||
|
@ -547,9 +547,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener {
|
|||||||
}
|
}
|
||||||
launchCatchingTask(resources.getString(R.string.card_template_editor_save_error)) {
|
launchCatchingTask(resources.getString(R.string.card_template_editor_save_error)) {
|
||||||
requireActivity().withProgress(resources.getString(R.string.saving_model)) {
|
requireActivity().withProgress(resources.getString(R.string.saving_model)) {
|
||||||
withCol {
|
withCol { tempModel!!.saveToDatabase() }
|
||||||
tempModel!!.saveToDatabase(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
onModelSaved()
|
onModelSaved()
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import androidx.core.os.bundleOf
|
|||||||
import com.ichi2.async.saveModel
|
import com.ichi2.async.saveModel
|
||||||
import com.ichi2.compat.CompatHelper.Companion.compat
|
import com.ichi2.compat.CompatHelper.Companion.compat
|
||||||
import com.ichi2.compat.CompatHelper.Companion.getSerializableCompat
|
import com.ichi2.compat.CompatHelper.Companion.getSerializableCompat
|
||||||
|
import com.ichi2.libanki.Collection
|
||||||
import com.ichi2.libanki.NoteTypeId
|
import com.ichi2.libanki.NoteTypeId
|
||||||
import com.ichi2.libanki.NotetypeJson
|
import com.ichi2.libanki.NotetypeJson
|
||||||
import com.ichi2.utils.KotlinCleanup
|
import com.ichi2.utils.KotlinCleanup
|
||||||
@ -87,11 +88,12 @@ class CardTemplateNotetype(val notetype: NotetypeJson) {
|
|||||||
addTemplateChange(ChangeType.DELETE, ord)
|
addTemplateChange(ChangeType.DELETE, ord)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveToDatabase(collection: com.ichi2.libanki.Collection) {
|
context(Collection)
|
||||||
|
fun saveToDatabase() {
|
||||||
Timber.d("saveToDatabase() called")
|
Timber.d("saveToDatabase() called")
|
||||||
dumpChanges()
|
dumpChanges()
|
||||||
clearTempModelFiles()
|
clearTempModelFiles()
|
||||||
return saveModel(collection, notetype, adjustedTemplateChanges)
|
return saveModel(notetype, adjustedTemplateChanges)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1559,7 +1559,7 @@ open class DeckPicker :
|
|||||||
withCol {
|
withCol {
|
||||||
Timber.i("RepairCollection: Closing collection")
|
Timber.i("RepairCollection: Closing collection")
|
||||||
close()
|
close()
|
||||||
BackupManager.repairCollection(this)
|
BackupManager.repairCollection()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@ -1618,7 +1618,7 @@ open class DeckPicker :
|
|||||||
launchCatchingTask {
|
launchCatchingTask {
|
||||||
// Number of deleted files
|
// Number of deleted files
|
||||||
val noOfDeletedFiles = withProgress(resources.getString(R.string.delete_media_message)) {
|
val noOfDeletedFiles = withProgress(resources.getString(R.string.delete_media_message)) {
|
||||||
withCol { deleteMedia(this, unused) }
|
withCol { deleteMedia(unused) }
|
||||||
}
|
}
|
||||||
showSimpleMessageDialog(
|
showSimpleMessageDialog(
|
||||||
title = resources.getString(R.string.delete_media_result_title),
|
title = resources.getString(R.string.delete_media_result_title),
|
||||||
@ -2127,7 +2127,7 @@ open class DeckPicker :
|
|||||||
Timber.d("rebuildFiltered: doInBackground - RebuildCram")
|
Timber.d("rebuildFiltered: doInBackground - RebuildCram")
|
||||||
decks.select(did)
|
decks.select(did)
|
||||||
sched.rebuildDyn(decks.selected())
|
sched.rebuildDyn(decks.selected())
|
||||||
updateValuesFromDeck(this)
|
updateValuesFromDeck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateDeckList()
|
updateDeckList()
|
||||||
@ -2142,7 +2142,7 @@ open class DeckPicker :
|
|||||||
withCol {
|
withCol {
|
||||||
Timber.d("doInBackgroundEmptyCram")
|
Timber.d("doInBackgroundEmptyCram")
|
||||||
sched.emptyDyn(decks.selected())
|
sched.emptyDyn(decks.selected())
|
||||||
updateValuesFromDeck(this)
|
updateValuesFromDeck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateDeckList()
|
updateDeckList()
|
||||||
|
@ -202,7 +202,7 @@ open class Reviewer :
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
when {
|
when {
|
||||||
stopTimerOnAnswer && isDisplayingAnswer -> {}
|
stopTimerOnAnswer && isDisplayingAnswer -> {}
|
||||||
else -> launchCatchingTask { withCol { answerTimer.resume(this) } }
|
else -> launchCatchingTask { withCol { answerTimer.resume() } }
|
||||||
}
|
}
|
||||||
super.onResume()
|
super.onResume()
|
||||||
if (typeAnswer?.autoFocusEditText() == true) {
|
if (typeAnswer?.autoFocusEditText() == true) {
|
||||||
@ -1015,7 +1015,7 @@ open class Reviewer :
|
|||||||
override suspend fun updateCurrentCard() {
|
override suspend fun updateCurrentCard() {
|
||||||
val state = withCol {
|
val state = withCol {
|
||||||
sched.currentQueueState()?.apply {
|
sched.currentQueueState()?.apply {
|
||||||
topCard.renderOutput(this@withCol, true)
|
topCard.renderOutput(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state?.timeboxReached?.let { dealWithTimeBox(it) }
|
state?.timeboxReached?.let { dealWithTimeBox(it) }
|
||||||
|
@ -283,7 +283,7 @@ class StudyOptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||||||
withCol {
|
withCol {
|
||||||
Timber.d("doInBackground - RebuildCram")
|
Timber.d("doInBackground - RebuildCram")
|
||||||
sched.rebuildDyn(decks.selected())
|
sched.rebuildDyn(decks.selected())
|
||||||
updateValuesFromDeck(this)
|
updateValuesFromDeck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rebuildUi(result, true)
|
rebuildUi(result, true)
|
||||||
@ -295,7 +295,7 @@ class StudyOptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||||||
withCol {
|
withCol {
|
||||||
Timber.d("doInBackgroundEmptyCram")
|
Timber.d("doInBackgroundEmptyCram")
|
||||||
sched.emptyDyn(decks.selected())
|
sched.emptyDyn(decks.selected())
|
||||||
updateValuesFromDeck(this)
|
updateValuesFromDeck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rebuildUi(result, true)
|
rebuildUi(result, true)
|
||||||
@ -446,7 +446,7 @@ class StudyOptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||||||
// Load the deck counts for the deck from Collection asynchronously
|
// Load the deck counts for the deck from Collection asynchronously
|
||||||
updateValuesFromDeckJob = launchCatchingTask {
|
updateValuesFromDeckJob = launchCatchingTask {
|
||||||
if (CollectionManager.isOpenUnsafe()) {
|
if (CollectionManager.isOpenUnsafe()) {
|
||||||
val result = withCol { updateValuesFromDeck(this) }
|
val result = withCol { updateValuesFromDeck() }
|
||||||
rebuildUi(result, resetDecklist)
|
rebuildUi(result, resetDecklist)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,7 @@ class CardBrowserViewModel(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
// PERF: slightly inefficient if the source was lastDeckId
|
// PERF: slightly inefficient if the source was lastDeckId
|
||||||
setDeckId(getInitialDeck())
|
setDeckId(getInitialDeck())
|
||||||
val cardsOrNotes = withCol { CardsOrNotes.fromCollection(this) }
|
val cardsOrNotes = withCol { CardsOrNotes.fromCollection() }
|
||||||
cardsOrNotesFlow.update { cardsOrNotes }
|
cardsOrNotesFlow.update { cardsOrNotes }
|
||||||
|
|
||||||
withCol {
|
withCol {
|
||||||
@ -260,7 +260,7 @@ class CardBrowserViewModel(
|
|||||||
fun setCardsOrNotes(newValue: CardsOrNotes) = viewModelScope.launch {
|
fun setCardsOrNotes(newValue: CardsOrNotes) = viewModelScope.launch {
|
||||||
withCol {
|
withCol {
|
||||||
// Change this to only change the preference on a state change
|
// Change this to only change the preference on a state change
|
||||||
newValue.saveToCollection(this)
|
newValue.saveToCollection()
|
||||||
}
|
}
|
||||||
cardsOrNotesFlow.update { newValue }
|
cardsOrNotesFlow.update { newValue }
|
||||||
}
|
}
|
||||||
@ -363,7 +363,7 @@ class CardBrowserViewModel(
|
|||||||
CARDS -> Pair(ExportDialogFragment.ExportType.Cards, selectedCardIds)
|
CARDS -> Pair(ExportDialogFragment.ExportType.Cards, selectedCardIds)
|
||||||
NOTES -> Pair(
|
NOTES -> Pair(
|
||||||
ExportDialogFragment.ExportType.Notes,
|
ExportDialogFragment.ExportType.Notes,
|
||||||
withCol { CardService.selectedNoteIds(selectedCardIds, this) }
|
withCol { CardService.selectedNoteIds(selectedCardIds) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ package com.ichi2.anki.cardviewer
|
|||||||
|
|
||||||
import androidx.annotation.CheckResult
|
import androidx.annotation.CheckResult
|
||||||
import com.ichi2.anki.CardUtils
|
import com.ichi2.anki.CardUtils
|
||||||
import com.ichi2.anki.CollectionManager.withCol
|
|
||||||
import com.ichi2.libanki.Card
|
import com.ichi2.libanki.Card
|
||||||
import com.ichi2.libanki.Collection
|
import com.ichi2.libanki.Collection
|
||||||
import com.ichi2.libanki.DeckId
|
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
|
fun appliesTo(card: Card): Boolean = CardUtils.getDeckIdForCard(card) == deckId
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
context(Collection)
|
||||||
@CheckResult
|
@CheckResult
|
||||||
fun create(collection: Collection, card: Card): CardSoundConfig {
|
fun create(card: Card): CardSoundConfig {
|
||||||
Timber.v("start loading SoundConfig")
|
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)
|
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)
|
Timber.d("loaded SoundConfig: %s", this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CheckResult
|
|
||||||
suspend fun create(card: Card): CardSoundConfig = withCol { create(this, card) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import com.ichi2.anki.AbstractFlashcardViewer
|
|||||||
import com.ichi2.anki.AndroidTtsError
|
import com.ichi2.anki.AndroidTtsError
|
||||||
import com.ichi2.anki.AndroidTtsError.TtsErrorCode
|
import com.ichi2.anki.AndroidTtsError.TtsErrorCode
|
||||||
import com.ichi2.anki.AndroidTtsPlayer
|
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.CONTINUE_AUDIO
|
||||||
import com.ichi2.anki.cardviewer.SoundErrorBehavior.RETRY_AUDIO
|
import com.ichi2.anki.cardviewer.SoundErrorBehavior.RETRY_AUDIO
|
||||||
import com.ichi2.anki.cardviewer.SoundErrorBehavior.STOP_AUDIO
|
import com.ichi2.anki.cardviewer.SoundErrorBehavior.STOP_AUDIO
|
||||||
@ -122,7 +123,7 @@ class SoundPlayer(
|
|||||||
this.side = side
|
this.side = side
|
||||||
|
|
||||||
if (!this::config.isInitialized || !config.appliesTo(card)) {
|
if (!this::config.isInitialized || !config.appliesTo(card)) {
|
||||||
config = CardSoundConfig.create(card)
|
config = withCol { CardSoundConfig.create(card) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ suspend fun rebuildCram(listener: CreateCustomStudySessionListener) {
|
|||||||
CollectionManager.withCol {
|
CollectionManager.withCol {
|
||||||
Timber.d("doInBackground - rebuildCram()")
|
Timber.d("doInBackground - rebuildCram()")
|
||||||
sched.rebuildDyn(decks.selected())
|
sched.rebuildDyn(decks.selected())
|
||||||
updateValuesFromDeck(this)
|
updateValuesFromDeck()
|
||||||
}
|
}
|
||||||
listener.onPostExecute()
|
listener.onPostExecute()
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,15 @@ enum class CardsOrNotes {
|
|||||||
CARDS,
|
CARDS,
|
||||||
NOTES;
|
NOTES;
|
||||||
|
|
||||||
fun saveToCollection(collection: Collection) {
|
context (Collection)
|
||||||
collection.config.setBool(ConfigKey.Bool.BROWSER_TABLE_SHOW_NOTES_MODE, this == NOTES)
|
fun saveToCollection() {
|
||||||
|
this@Collection.config.setBool(ConfigKey.Bool.BROWSER_TABLE_SHOW_NOTES_MODE, this == NOTES)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromCollection(col: Collection): CardsOrNotes =
|
context (Collection)
|
||||||
when (col.config.getBool(ConfigKey.Bool.BROWSER_TABLE_SHOW_NOTES_MODE)) {
|
fun fromCollection(): CardsOrNotes =
|
||||||
|
when (this@Collection.config.getBool(ConfigKey.Bool.BROWSER_TABLE_SHOW_NOTES_MODE)) {
|
||||||
true -> NOTES
|
true -> NOTES
|
||||||
false -> CARDS
|
false -> CARDS
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import com.ichi2.anki.servicelayer.MARKED_TAG
|
|||||||
import com.ichi2.anki.servicelayer.NoteService
|
import com.ichi2.anki.servicelayer.NoteService
|
||||||
import com.ichi2.libanki.Card
|
import com.ichi2.libanki.Card
|
||||||
import com.ichi2.libanki.Sound.addPlayButtons
|
import com.ichi2.libanki.Sound.addPlayButtons
|
||||||
|
import com.ichi2.libanki.note
|
||||||
import com.ichi2.themes.Themes
|
import com.ichi2.themes.Themes
|
||||||
import com.ichi2.utils.toRGBHex
|
import com.ichi2.utils.toRGBHex
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
@ -90,8 +91,7 @@ class PreviewerViewModel(private val selectedCardIds: LongArray, firstIndex: Int
|
|||||||
|
|
||||||
fun toggleMark() {
|
fun toggleMark() {
|
||||||
launchCatching {
|
launchCatching {
|
||||||
// TODO: Consider a context receiver
|
val note = withCol { currentCard.note() }
|
||||||
val note = withCol { currentCard.note(this) }
|
|
||||||
NoteService.toggleMark(note)
|
NoteService.toggleMark(note)
|
||||||
isMarked.emit(NoteService.isMarked(note))
|
isMarked.emit(NoteService.isMarked(note))
|
||||||
}
|
}
|
||||||
@ -132,7 +132,7 @@ class PreviewerViewModel(private val selectedCardIds: LongArray, firstIndex: Int
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateMarkIcon() {
|
private suspend fun updateMarkIcon() {
|
||||||
val note = withCol { currentCard.note(this) }
|
val note = withCol { currentCard.note() }
|
||||||
isMarked.emit(note.hasTag(MARKED_TAG))
|
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? {
|
private suspend fun getExpectedTypeInAnswer(card: Card, field: JSONObject): String? {
|
||||||
val fieldName = field.getString("name")
|
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:")) {
|
return if (fieldName.startsWith("cloze:")) {
|
||||||
val clozeIdx = card.ord + 1
|
val clozeIdx = card.ord + 1
|
||||||
withCol {
|
withCol {
|
||||||
|
@ -135,3 +135,9 @@ class AnswerTimer(private val cardTimer: Chronometer) {
|
|||||||
private val elapsedRealTime
|
private val elapsedRealTime
|
||||||
get() = SystemClock.elapsedRealtime()
|
get() = SystemClock.elapsedRealtime()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @see AnswerTimer.resume */
|
||||||
|
context (Collection)
|
||||||
|
fun AnswerTimer.resume() {
|
||||||
|
this@AnswerTimer.resume(this@Collection)
|
||||||
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package com.ichi2.anki.servicelayer
|
package com.ichi2.anki.servicelayer
|
||||||
|
|
||||||
import com.ichi2.anki.CardUtils
|
import com.ichi2.anki.CardUtils
|
||||||
|
import com.ichi2.libanki.CardId
|
||||||
import com.ichi2.libanki.Collection
|
import com.ichi2.libanki.Collection
|
||||||
|
|
||||||
object CardService {
|
object CardService {
|
||||||
@ -24,11 +25,11 @@ object CardService {
|
|||||||
* get unique note ids from a list of card ids
|
* get unique note ids from a list of card ids
|
||||||
* @param selectedCardIds list of card ids
|
* @param selectedCardIds list of card ids
|
||||||
* can do better with performance here
|
* 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(
|
CardUtils.getNotes(
|
||||||
col,
|
this@Collection,
|
||||||
selectedCardIds.map { col.getCard(it) }
|
selectedCardIds.map { this@Collection.getCard(it) }
|
||||||
).map { it.id }
|
).map { it.id }
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
try {
|
||||||
withCol { deleteMedia(this, filesNamesToDelete) }
|
withCol { deleteMedia(filesNamesToDelete) }
|
||||||
} finally {
|
} finally {
|
||||||
launchCalculationOfSizeOfEverything()
|
launchCalculationOfSizeOfEverything()
|
||||||
launchCalculationOfCollectionSize()
|
launchCalculationOfCollectionSize()
|
||||||
@ -108,7 +108,7 @@ class ManageSpaceViewModel(val app: Application) : AndroidViewModel(app), Collec
|
|||||||
|
|
||||||
suspend fun deleteBackups(backupsToDelete: List<File>) {
|
suspend fun deleteBackups(backupsToDelete: List<File>) {
|
||||||
try {
|
try {
|
||||||
withCol { BackupManager.deleteBackups(this, backupsToDelete) }
|
withCol { BackupManager.deleteBackups(backupsToDelete) }
|
||||||
} finally {
|
} finally {
|
||||||
launchCalculationOfBackupsSize()
|
launchCalculationOfBackupsSize()
|
||||||
launchCalculationOfCollectionSize()
|
launchCalculationOfCollectionSize()
|
||||||
@ -225,7 +225,7 @@ class ManageSpaceFragment : SettingsFragment() {
|
|||||||
val filesNamesToDelete = unusedFileNames.filterIndexed { index, _ -> checkedItems[index] }
|
val filesNamesToDelete = unusedFileNames.filterIndexed { index, _ -> checkedItems[index] }
|
||||||
|
|
||||||
withProgress(R.string.delete_media_message) {
|
withProgress(R.string.delete_media_message) {
|
||||||
viewModel.deleteMedia(filesNamesToDelete)
|
viewModel.deleteMediaFiles(filesNamesToDelete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -27,33 +27,23 @@ import timber.log.Timber
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file contains functions that have been migrated from [CollectionTask]
|
* Takes a list of media file names and removes them from the [Collection]
|
||||||
* 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
|
|
||||||
* @param unused List of media names to be deleted
|
* @param unused List of media names to be deleted
|
||||||
*/
|
*/
|
||||||
fun deleteMedia(
|
context (Collection)
|
||||||
col: Collection,
|
fun deleteMedia(unused: List<String>): Int {
|
||||||
unused: List<String>
|
|
||||||
): Int {
|
|
||||||
// FIXME: this provides progress info that is not currently used
|
// FIXME: this provides progress info that is not currently used
|
||||||
col.media.removeFiles(unused)
|
this@Collection.media.removeFiles(unused)
|
||||||
return unused.size
|
return unused.size
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Once [com.ichi2.async.CollectionTask.RebuildCram] and [com.ichi2.async.CollectionTask.EmptyCram]
|
// 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]
|
// are migrated to Coroutines, move this function to [com.ichi2.anki.StudyOptionsFragment]
|
||||||
fun updateValuesFromDeck(
|
context (Collection)
|
||||||
col: Collection
|
fun updateValuesFromDeck(): StudyOptionsFragment.DeckStudyData? {
|
||||||
): StudyOptionsFragment.DeckStudyData? {
|
|
||||||
Timber.d("doInBackgroundUpdateValuesFromDeck")
|
Timber.d("doInBackgroundUpdateValuesFromDeck")
|
||||||
return try {
|
return try {
|
||||||
val sched = col.sched
|
val sched = this@Collection.sched
|
||||||
val counts = sched.counts()
|
val counts = sched.counts()
|
||||||
val totalNewCount = sched.totalNewForCurrentDeck()
|
val totalNewCount = sched.totalNewForCurrentDeck()
|
||||||
val totalCount = sched.cardCount()
|
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
|
* 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
|
* @return Pair<Boolean, String> : (true, null) when success, (false, exceptionMessage) when failure
|
||||||
*/
|
*/
|
||||||
|
context (Collection)
|
||||||
fun saveModel(
|
fun saveModel(
|
||||||
col: Collection,
|
|
||||||
notetype: NotetypeJson,
|
notetype: NotetypeJson,
|
||||||
templateChanges: ArrayList<Array<Any>>
|
templateChanges: ArrayList<Array<Any>>
|
||||||
) {
|
) {
|
||||||
Timber.d("doInBackgroundSaveModel")
|
Timber.d("doInBackgroundSaveModel")
|
||||||
val oldModel = col.notetypes.get(notetype.getLong("id"))
|
val oldModel = this@Collection.notetypes.get(notetype.getLong("id"))
|
||||||
|
|
||||||
// TODO: make undoable
|
// TODO: make undoable
|
||||||
val newTemplates = notetype.getJSONArray("tmpls")
|
val newTemplates = notetype.getJSONArray("tmpls")
|
||||||
@ -140,11 +130,11 @@ fun saveModel(
|
|||||||
when (change[1] as CardTemplateNotetype.ChangeType) {
|
when (change[1] as CardTemplateNotetype.ChangeType) {
|
||||||
CardTemplateNotetype.ChangeType.ADD -> {
|
CardTemplateNotetype.ChangeType.ADD -> {
|
||||||
Timber.d("doInBackgroundSaveModel() adding template %s", change[0])
|
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 -> {
|
CardTemplateNotetype.ChangeType.DELETE -> {
|
||||||
Timber.d("doInBackgroundSaveModel() deleting template currently at ordinal %s", change[0])
|
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
|
// required for Rust: the modified time can't go backwards, and we updated the model by adding fields
|
||||||
// This could be done better
|
// This could be done better
|
||||||
notetype.put("mod", oldModel!!.getLong("mod"))
|
notetype.put("mod", oldModel!!.getLong("mod"))
|
||||||
col.notetypes.save(notetype)
|
this@Collection.notetypes.save(notetype)
|
||||||
col.notetypes.update(notetype)
|
this@Collection.notetypes.update(notetype)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -17,8 +17,9 @@
|
|||||||
package com.ichi2.anki.cardviewer
|
package com.ichi2.anki.cardviewer
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.ichi2.anki.CollectionManager.withCol
|
||||||
|
import com.ichi2.libanki.Card
|
||||||
import com.ichi2.testutils.JvmTest
|
import com.ichi2.testutils.JvmTest
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
import org.hamcrest.Matchers.equalTo
|
import org.hamcrest.Matchers.equalTo
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
@ -34,7 +35,7 @@ class CardSoundConfigTest : JvmTest() {
|
|||||||
// defaults as-of Anki Desktop 23.10 (51a10f09)
|
// defaults as-of Anki Desktop 23.10 (51a10f09)
|
||||||
val note = addNoteUsingBasicModel()
|
val note = addNoteUsingBasicModel()
|
||||||
val card = note.firstCard()
|
val card = note.firstCard()
|
||||||
CardSoundConfig.create(card).run {
|
createCardSoundConfig(card).run {
|
||||||
assertThat("deckId", deckId, equalTo(card.did))
|
assertThat("deckId", deckId, equalTo(card.did))
|
||||||
// Anki Desktop: "Skip question when replaying answer" -> false
|
// Anki Desktop: "Skip question when replaying answer" -> false
|
||||||
// our variable is reversed, so true
|
// our variable is reversed, so true
|
||||||
@ -49,7 +50,7 @@ class CardSoundConfigTest : JvmTest() {
|
|||||||
fun `cards from the same note are equal`() = runTest {
|
fun `cards from the same note are equal`() = runTest {
|
||||||
val note = addNoteUsingBasicAndReversedModel()
|
val note = addNoteUsingBasicAndReversedModel()
|
||||||
val (card1, card2) = note.cards()
|
val (card1, card2) = note.cards()
|
||||||
CardSoundConfig.create(card1).run {
|
createCardSoundConfig(card1).run {
|
||||||
assertThat("same note", this.appliesTo(card2))
|
assertThat("same note", this.appliesTo(card2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,7 +58,7 @@ class CardSoundConfigTest : JvmTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `cards from the same deck are equal`() = runTest {
|
fun `cards from the same deck are equal`() = runTest {
|
||||||
val (note1, note2) = addNotes(count = 2)
|
val (note1, note2) = addNotes(count = 2)
|
||||||
CardSoundConfig.create(note1.firstCard()).run {
|
createCardSoundConfig(note1.firstCard()).run {
|
||||||
assertThat("same note", this.appliesTo(note2.firstCard()))
|
assertThat("same note", this.appliesTo(note2.firstCard()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,4 +67,6 @@ class CardSoundConfigTest : JvmTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `cards with the same deck options are equal`() {
|
fun `cards with the same deck options are equal`() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun createCardSoundConfig(card: Card) = withCol { CardSoundConfig.create(card) }
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ subprojects {
|
|||||||
tasks.withType(KotlinCompile).configureEach {
|
tasks.withType(KotlinCompile).configureEach {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
allWarningsAsErrors = fatalWarnings
|
allWarningsAsErrors = fatalWarnings
|
||||||
def compilerArgs = ['-Xjvm-default=all']
|
def compilerArgs = ['-Xjvm-default=all', '-Xcontext-receivers']
|
||||||
if (project.name != "api") {
|
if (project.name != "api") {
|
||||||
compilerArgs += ['-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi']
|
compilerArgs += ['-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi']
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user