Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9f845f0

Browse files
Introduce ChangeNoteType Dialog
This dialog allows the bulk remapping of either fields or card templates to a different note type A full sync is required for this operation inputs: * output note type * a map of fields (based on the output) * a map of templates (based on the output) * only if both input and output are non-cloze Fixes 14134 Co-authored-by: David Allison <[email protected]>
1 parent c7f5425 commit 9f845f0

File tree

32 files changed

+2488
-23
lines changed

32 files changed

+2488
-23
lines changed

AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import com.ichi2.anki.browser.CardBrowserLaunchOptions
5555
import com.ichi2.anki.browser.CardBrowserViewModel
5656
import com.ichi2.anki.browser.CardBrowserViewModel.ChangeMultiSelectMode
5757
import com.ichi2.anki.browser.CardBrowserViewModel.ChangeMultiSelectMode.SingleSelectCause
58+
import com.ichi2.anki.browser.CardBrowserViewModel.ChangeNoteTypeResponse
5859
import com.ichi2.anki.browser.CardBrowserViewModel.SearchState
5960
import com.ichi2.anki.browser.CardBrowserViewModel.SearchState.Initializing
6061
import com.ichi2.anki.browser.CardBrowserViewModel.SearchState.Searching
@@ -67,6 +68,7 @@ import com.ichi2.anki.browser.toCardBrowserLaunchOptions
6768
import com.ichi2.anki.common.annotations.NeedsTest
6869
import com.ichi2.anki.common.utils.annotation.KotlinCleanup
6970
import com.ichi2.anki.databinding.ActivityCardBrowserBinding
71+
import com.ichi2.anki.dialogs.ChangeNoteTypeDialog
7072
import com.ichi2.anki.dialogs.DeckSelectionDialog.DeckSelectionListener
7173
import com.ichi2.anki.dialogs.DiscardChangesDialog
7274
import com.ichi2.anki.dialogs.GradeNowDialog
@@ -546,6 +548,19 @@ open class CardBrowser :
546548
showDialogFragment(dialog)
547549
}
548550

551+
fun onChangeNoteType(result: ChangeNoteTypeResponse) {
552+
when (result) {
553+
ChangeNoteTypeResponse.NoSelection -> {
554+
Timber.w("change note type: no selection")
555+
}
556+
ChangeNoteTypeResponse.MixedSelection -> showSnackbar(R.string.different_note_types_selected)
557+
is ChangeNoteTypeResponse.ChangeNoteType -> {
558+
val dialog = ChangeNoteTypeDialog.newInstance(result.noteIds)
559+
showDialogFragment(dialog)
560+
}
561+
}
562+
}
563+
549564
viewModel.flowOfSearchQueryExpanded.launchCollectionInLifecycleScope(::onSearchQueryExpanded)
550565
viewModel.flowOfSelectedRows.launchCollectionInLifecycleScope(::onSelectedRowsChanged)
551566
viewModel.flowOfFilterQuery.launchCollectionInLifecycleScope(::onFilterQueryChanged)
@@ -555,6 +570,7 @@ open class CardBrowser :
555570
viewModel.flowOfSearchState.launchCollectionInLifecycleScope(::searchStateChanged)
556571
viewModel.cardSelectionEventFlow.launchCollectionInLifecycleScope(::onSelectedCardUpdated)
557572
viewModel.flowOfSaveSearchNamePrompt.launchCollectionInLifecycleScope(::onSaveSearchNamePrompt)
573+
viewModel.flowOfChangeNoteType.launchCollectionInLifecycleScope(::onChangeNoteType)
558574
}
559575

560576
fun isKeyboardVisible(view: View?): Boolean =
@@ -665,6 +681,13 @@ open class CardBrowser :
665681
return true
666682
}
667683
}
684+
KeyEvent.KEYCODE_M -> {
685+
if (event.isCtrlPressed && event.isShiftPressed) {
686+
Timber.i("Ctrl+Shift+M: Change Note Type")
687+
viewModel.requestChangeNoteType()
688+
return true
689+
}
690+
}
668691
KeyEvent.KEYCODE_Z -> {
669692
if (event.isCtrlPressed) {
670693
Timber.i("Ctrl+Z: Undo")
@@ -854,6 +877,7 @@ open class CardBrowser :
854877
isVisible = isFindReplaceEnabled
855878
title = TR.browsingFindAndReplace().toSentenceCase(this@CardBrowser, R.string.sentence_find_and_replace)
856879
}
880+
857881
previewItem = menu.findItem(R.id.action_preview)
858882
onSelectionChanged()
859883
refreshMenuItems()
@@ -1016,6 +1040,11 @@ open class CardBrowser :
10161040
showSavedSearches()
10171041
return true
10181042
}
1043+
R.id.action_change_note_type -> {
1044+
Timber.i("Menu: Change note type")
1045+
viewModel.requestChangeNoteType()
1046+
return true
1047+
}
10191048
R.id.action_undo -> {
10201049
Timber.w("CardBrowser:: Undo pressed")
10211050
onUndo()

AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2495,7 +2495,7 @@ class OneWaySyncDialog(
24952495
/**
24962496
* [launchCatchingTask], showing a one-way sync dialog: [R.string.full_sync_confirmation]
24972497
*/
2498-
private fun AnkiActivity.launchCatchingRequiringOneWaySync(block: suspend () -> Unit) =
2498+
fun AnkiActivity.launchCatchingRequiringOneWaySync(block: suspend () -> Unit) =
24992499
launchCatchingTask {
25002500
try {
25012501
block()

AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,7 @@ class NoteEditorFragment :
14361436
* Change the note type from oldNoteType to newNoteType, handling the case where a full sync will be required
14371437
*/
14381438
@NeedsTest("test changing note type")
1439+
@Suppress("Deprecation") // Replace with ChangeNoteTypeDialog
14391440
private fun changeNoteType(
14401441
oldNotetype: NotetypeJson,
14411442
newNotetype: NotetypeJson,

AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserViewModel.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import com.ichi2.anki.libanki.Card
5656
import com.ichi2.anki.libanki.CardId
5757
import com.ichi2.anki.libanki.CardType
5858
import com.ichi2.anki.libanki.DeckId
59+
import com.ichi2.anki.libanki.NoteId
5960
import com.ichi2.anki.libanki.QueueType
6061
import com.ichi2.anki.libanki.QueueType.ManuallyBuried
6162
import com.ichi2.anki.libanki.QueueType.SiblingBuried
@@ -267,6 +268,8 @@ class CardBrowserViewModel(
267268
*/
268269
val flowOfCardStateChanged = MutableSharedFlow<Unit>()
269270

271+
val flowOfChangeNoteType = MutableSharedFlow<ChangeNoteTypeResponse>()
272+
270273
/**
271274
* Opens a prompt for the user to input a saved search name
272275
*
@@ -284,6 +287,19 @@ class CardBrowserViewModel(
284287

285288
suspend fun queryAllSelectedNoteIds() = selectedRows.queryNoteIds(this.cardsOrNotes)
286289

290+
fun requestChangeNoteType() =
291+
viewModelScope.launch {
292+
val noteIds = queryAllSelectedNoteIds()
293+
Timber.i("requestChangeNoteType: querying %d selected notes", noteIds.size)
294+
flowOfChangeNoteType.emit(
295+
when {
296+
noteIds.isEmpty() -> ChangeNoteTypeResponse.NoSelection
297+
!noteIds.allOfSameNoteType() -> ChangeNoteTypeResponse.MixedSelection
298+
else -> ChangeNoteTypeResponse.ChangeNoteType.from(noteIds)
299+
},
300+
)
301+
}
302+
287303
@VisibleForTesting
288304
internal suspend fun queryAllCardIds() = cards.queryCardIds()
289305

@@ -1351,6 +1367,25 @@ class CardBrowserViewModel(
13511367
SELECT_NONE,
13521368
}
13531369

1370+
sealed interface ChangeNoteTypeResponse {
1371+
data object NoSelection : ChangeNoteTypeResponse
1372+
1373+
data object MixedSelection : ChangeNoteTypeResponse
1374+
1375+
@ConsistentCopyVisibility
1376+
data class ChangeNoteType private constructor(
1377+
val noteIds: List<NoteId>,
1378+
) : ChangeNoteTypeResponse {
1379+
companion object {
1380+
@CheckResult
1381+
fun from(ids: List<NoteId>): ChangeNoteType {
1382+
require(ids.isNotEmpty()) { "a non-empty list must be provided" }
1383+
return ChangeNoteType(ids.distinct())
1384+
}
1385+
}
1386+
}
1387+
}
1388+
13541389
/**
13551390
* @param wasBuried `true` if all cards were buried, `false` if unburied
13561391
* @param count the number of affected cards
@@ -1515,6 +1550,16 @@ sealed class RepositionCardsRequest {
15151550

15161551
fun BrowserColumns.Column.getLabel(cardsOrNotes: CardsOrNotes): String = if (cardsOrNotes == CARDS) cardsModeLabel else notesModeLabel
15171552

1553+
/**
1554+
* Whether the provided notes all have the same the same [note type][com.ichi2.anki.libanki.NoteTypeId]
1555+
*/
1556+
private suspend fun List<NoteId>.allOfSameNoteType(): Boolean {
1557+
val noteIds = this
1558+
return withCol { notetypes.nids(getNote(noteIds.first()).noteTypeId) }.toSet().let { set ->
1559+
noteIds.all { set.contains(it) }
1560+
}
1561+
}
1562+
15181563
@Parcelize
15191564
data class ColumnHeading(
15201565
val label: String,

0 commit comments

Comments
 (0)