Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2598,6 +2598,54 @@ class ContentProviderTest : InstrumentedTest() {
}
}

/**
* Check that set the flag in card works as expected
*/
@Test
fun testSetFlagCard() {
val noteId = createdNotes.first().lastPathSegment!!.toLong()
val noteUri = Uri.withAppendedPath(FlashCardsContract.Note.CONTENT_URI, noteId.toString())
val noteCardsUri = Uri.withAppendedPath(noteUri, "cards")
val card = col.getNote(noteId).cards(col).single()
val noteCardUri = Uri.withAppendedPath(noteCardsUri, card.ord.toString())

val negativeValue =
ContentValues().apply {
put(FlashCardsContract.Card.FLAGS, -1)
}
val exception1 = assertThrows<IllegalArgumentException> { contentResolver.update(noteCardUri, negativeValue, null, null) }
assertEquals(exception1.message, "Flags value must be in the range from 0 to 7")

val tooLargeValue =
ContentValues().apply {
put(FlashCardsContract.Card.FLAGS, 8)
}
val exception2 = assertThrows<IllegalArgumentException> { contentResolver.update(noteCardUri, tooLargeValue, null, null) }
assertEquals(exception2.message, "Flags value must be in the range from 0 to 7")
Comment thread
ColgateTotal77 marked this conversation as resolved.
Outdated

val correctValue =
ContentValues().apply {
put(FlashCardsContract.Card.FLAGS, 4)
}
contentResolver.update(noteCardUri, correctValue, null, null)

val cursor =
checkNotNull(
contentResolver.query(
noteCardUri,
arrayOf(FlashCardsContract.Card.FLAGS),
null,
null,
null,
),
) { "cursor from /notes/#/cards/#" }

cursor.use {
assertTrue(it.moveToFirst())
assertEquals(4, it.getInt(it.getColumnIndex(FlashCardsContract.Card.FLAGS)))
}
}

// TODO: PERF: use TestChangeSubscriber once we've moved to testFixtures
private class TestSubscriber : ChangeManager.Subscriber {
var count = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,28 +538,38 @@ class CardContentProvider : ContentProvider() {
NOTES_ID_CARDS -> throw UnsupportedOperationException("Not yet implemented")
NOTES_ID_CARDS_ORD -> {
val currentCard = getCardFromUri(uri, col)
var isDeckUpdate = false
var did = Decks.NOT_FOUND_DECK_ID
// the key of the ContentValues contains the column name
// the value of the ContentValues contains the row value.
val valueSet = values!!.valueSet()
for ((key) in valueSet) {
// Only updates on deck id is supported
isDeckUpdate = key == FlashCardsContract.Card.DECK_ID
did = values.getAsLong(key)
}
require(!col.decks.isFiltered(did)) { "Cards cannot be moved to a filtered deck" }
/* now update the card
*/
if (isDeckUpdate && did >= 0) {
Timber.d("CardContentProvider: Moving card to other deck...")
currentCard.did = did
col.updateCard(currentCard)
when (key) {
FlashCardsContract.Card.DECK_ID -> {
val did = values.getAsLong(key)
require(!col.decks.isFiltered(did)) { "Cards cannot be moved to a filtered deck" }

updated++
} else {
// User tries an operation that is not (yet?) supported.
throw IllegalArgumentException("Currently only updates of decks are supported")
Timber.d("CardContentProvider: Moving card to other deck...")
currentCard.did = did
col.updateCard(currentCard)

updated++
}
FlashCardsContract.Card.FLAGS -> {
val flags = values.getAsInteger(key)

if (flags == null || flags < 0 || flags > 7) {
throw IllegalArgumentException("Flags value must be in the range from 0 to 7")
}

Timber.d("CardContentProvider: flags update...")
currentCard.flags = flags
Comment thread
ColgateTotal77 marked this conversation as resolved.
Outdated
col.updateCard(currentCard)
updated++
}
else -> {
// User tries an operation that is not (yet?) supported.
throw IllegalArgumentException("Currently only updates of decks and flags are supported")
}
}
}
}
NOTE_TYPES -> throw IllegalArgumentException("Cannot update models in bulk")
Expand Down Expand Up @@ -1223,6 +1233,7 @@ class CardContentProvider : ContentProvider() {
FlashCardsContract.Card.FSRS_DESIRED_RETENTION -> rb.add(currentCard.fsrsDesiredRetention)
FlashCardsContract.Card.FSRS_DECAY -> rb.add(currentCard.decay)
FlashCardsContract.Card.LAST_REVIEW_TIME_SECONDS -> rb.add(currentCard.lastReviewTimeSecs)
FlashCardsContract.Card.FLAGS -> rb.add(currentCard.flags)
Comment thread
ColgateTotal77 marked this conversation as resolved.
Outdated
else -> throw UnsupportedOperationException("Queue \"$column\" is unknown")
}
}
Expand Down
17 changes: 17 additions & 0 deletions api/src/main/java/com/ichi2/anki/FlashCardsContract.kt
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,22 @@ public object FlashCardsContract {
*/
public const val LAST_REVIEW_TIME_SECONDS: String = "last_review_time_secs"

/**
* The raw flag code in the range from 0 to 7
*
* * `0` = no flag
* * `1` = red
* * `2` = orange
* * `3` = green
* * `4` = blue
* * `5` = pink
* * `6` = turquoise
* * `7` = purple
*
* Other values will throw IllegalArgumentException on set.
*/
public const val FLAGS: String = "flags"

/**
* The content:// style URI for cards. Can be used to search for cards or access specific cards.
* For examples on how to use the URI for queries see the overview in [FlashCardsContract].
Expand All @@ -907,6 +923,7 @@ public object FlashCardsContract {
DECK_ID,
QUESTION,
ANSWER,
FLAGS,
)

/**
Expand Down
55 changes: 55 additions & 0 deletions api/src/main/java/com/ichi2/anki/api/AddContentApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,61 @@ public class AddContentApi(
}
}

/**
* Set the flag using flag code for a given note
* @param noteId the ID of the note to update
* @param rawFlag the flag code in the range from 0 to 7 to set
* @return true if flag was updated, otherwise false
* @throws SecurityException if READ_WRITE_PERMISSION not granted (e.g. due to install order bug)
* @throws IllegalArgumentException if flag code not in the range from 0 to 7
*/
public fun setFlagRaw(
noteId: Long,
rawFlag: Int,
): Boolean {
val cardsUri =
Note.CONTENT_URI
.buildUpon()
.appendPath(noteId.toString())
.appendPath("cards")
.build()

val cardsQuery = resolver.query(cardsUri, null, null, null, null) ?: return false

val values =
ContentValues().apply {
put(Card.FLAGS, rawFlag)
}
var numRowsUpdated = 0

cardsQuery.use { cardsCursor ->
while (cardsCursor.moveToNext()) {
val cardUri =
Note.CONTENT_URI
.buildUpon()
.appendPath(noteId.toString())
.appendPath("cards")
.appendPath(cardsCursor.getString(cardsCursor.getColumnIndex(Card.CARD_ORD)))
.build()
numRowsUpdated += resolver.update(cardUri, values, null, null)
}
}

return numRowsUpdated > 0
}

/**
* Set the flag for a given note
* @param noteId the ID of the note to update
* @param flag the flag to set
* @return true if flag was updated, otherwise false
* @throws SecurityException if READ_WRITE_PERMISSION not granted (e.g. due to install order bug)
*/
public fun setFlag(
noteId: Long,
flag: Flag,
): Boolean = setFlagRaw(noteId, flag.code)

/**
* Get the name of the selected deck
* @return deck name or null if there was a problem
Expand Down
21 changes: 21 additions & 0 deletions api/src/main/java/com/ichi2/anki/api/Flag.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 ColgateTotal77 <serega2005n@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-or-later
package com.ichi2.anki.api

/*
* [code] should be kept in sync with the [com.ichi2.anki.Flag] enum.
*
* param code The code of the flag.
*/
public enum class Flag(
Comment thread
ColgateTotal77 marked this conversation as resolved.
public val code: Int,
) {
NONE(0),
RED(1),
ORANGE(2),
GREEN(3),
BLUE(4),
PINK(5),
TURQUOISE(6),
PURPLE(7),
}
Loading