-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Introduce ChangeNoteType Dialog #18602
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Important Maintainers: This PR contains Strings changes
|
94aef84 to
9fd2a79
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
People have been wanting this for AGES, thanks so much!! 🥳🥳🥳🥳🥳
Looks great, cheers!
SO many users will love this, let me know if you want me to dig in and rebase/force push to get this up to scratch
Crash (resolved)
1 card: Basic
- Go to 'Templates'
- Change' Type' to 'Basic & Reversed'
Crash
2025-06-22 21:38:42.737 24328-24328 ChangeNote...ateSpinner com.ichi2.anki.debug D Updating card mapping: old template 0 -> new template 0
2025-06-22 21:38:42.738 24328-24328 ChangeNote...ateSpinner com.ichi2.anki.debug D Updating card mapping: old template 1 -> new template null
2025-06-22 21:38:42.738 24328-24328 ChangeNoteTypeViewModel com.ichi2.anki.debug W Attempted to update card mapping before initialization
2025-06-22 21:38:42.757 24328-24328 ThrowableFilterService com.ichi2.anki.debug V exceptionIsUnwanted - examining IndexOutOfBoundsException
2025-06-22 21:38:42.757 24328-24328 ThrowableFilterService com.ichi2.anki.debug V exceptionIsUnwanted - exception was wanted
2025-06-22 21:38:42.757 24328-24328 UsageAnalytics com.ichi2.anki.debug D sendAnalyticsException() description/fatal: java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1/true
2025-06-22 21:38:42.763 24328-24328 ACRA com.ichi2.anki.debug E ACRA caught a IndexOutOfBoundsException for com.ichi2.anki.debug
java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
at jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
at jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
at jdk.internal.util.Preconditions.checkIndex(Preconditions.java:266)
at java.util.Objects.checkIndex(Objects.java:385)
at java.util.ArrayList.get(ArrayList.java:434)
at com.ichi2.anki.dialogs.ChangeNoteTypeViewModel.getDiscardedCards(ChangeNoteTypeViewModel.kt:261)
at com.ichi2.anki.dialogs.ChangeNoteTypeViewModel$getDiscardedCards$1.invokeSuspend(Unknown Source:20)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
at android.os.Handler.handleCallback(Handler.java:995)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loopOnce(Looper.java:248)
at android.os.Looper.loop(Looper.java:338)
at android.app.ActivityThread.main(ActivityThread.java:9067)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:932)
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@6e28089, Dispatchers.Main.immediate]
AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserViewModel.kt
Outdated
Show resolved
Hide resolved
AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt
Outdated
Show resolved
Hide resolved
AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt
Outdated
Show resolved
Hide resolved
This comment was marked as resolved.
This comment was marked as resolved.
8e5adbe to
0e5bebe
Compare
david-allison
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main blocker is the color in night mode.
I think the rest are nitpicks, but it'd be great to get this as close to 'production-ready' as possible
| super.onConfigurationChanged(newConfig) | ||
| if (getScreenRotation() != initialRotation) { | ||
| Timber.d("recreating activity: orientation changed with 'Change Note Type' open") | ||
| requireAnkiActivity().recreate() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is necessary after 46cc209
Index: AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt (revision 9c3be7f12d11c2b4f570cdcb4df0a86941efe9f7)
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt (date 1764830377708)
@@ -19,7 +19,6 @@
import android.app.Dialog
import android.content.Context
-import android.content.res.Configuration
import android.graphics.Color
import android.os.Bundle
import android.text.SpannableStringBuilder
@@ -106,22 +105,11 @@
class ChangeNoteTypeDialog : AnalyticsDialogFragment() {
private val viewModel: ChangeNoteTypeViewModel by viewModels { defaultViewModelProviderFactory }
- private var initialRotation: Int = 0
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- this.initialRotation = getScreenRotation()
setupFlows()
}
- override fun onConfigurationChanged(newConfig: Configuration) {
- super.onConfigurationChanged(newConfig)
- if (getScreenRotation() != initialRotation) {
- Timber.d("recreating activity: orientation changed with 'Change Note Type' open")
- requireAnkiActivity().recreate()
- }
- }
-
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = ChangeNoteTypeDialogBinding.inflate(LayoutInflater.from(requireContext()))
@@ -291,8 +279,6 @@
tab.customView = binding.root
}
- private fun getScreenRotation() = ContextCompat.getDisplayOrDefault(requireContext()).rotation
-
/** Display model for note types in the spinner */
private data class DisplayNoteType(
val name: String,
AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt
Outdated
Show resolved
Hide resolved
AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt
Outdated
Show resolved
Hide resolved
AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt
Outdated
Show resolved
Hide resolved
| // unchanged after init { } | ||
| // ************************************************ | ||
|
|
||
| // This should be lateinit, but isn't due to NotetypeJson being `value class` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe... conversionTypeFlow may be a problematic change
Index: AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt (revision 9c3be7f12d11c2b4f570cdcb4df0a86941efe9f7)
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt (date 1764830853644)
@@ -50,6 +50,7 @@
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.launch
import timber.log.Timber
+import kotlin.properties.Delegates.notNull
private typealias TemplateIndex = Int
private typealias FieldIndex = Int
@@ -93,12 +94,9 @@
// unchanged after init { }
// ************************************************
- // This should be lateinit, but isn't due to NotetypeJson being `value class`
- private var _inputNoteType: NotetypeJson? = null
-
/** The note type of the notes to be modified */
- val inputNoteType: NotetypeJson
- get() = _inputNoteType!!
+ var inputNoteType by notNull<NotetypeJson>()
+ private set
/**
* The note types which a user can provide to [setOutputNoteTypeId]
@@ -143,8 +141,7 @@
val conversionTypeFlow: StateFlow<ConversionType> by lazy {
outputNoteTypeFlow
.transform { newNoteType ->
- val source = _inputNoteType ?: return@transform
- emit(ConversionType.fromNoteTypeChange(current = source, new = newNoteType))
+ emit(ConversionType.fromNoteTypeChange(current = inputNoteType, new = newNoteType))
}.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
@@ -231,7 +228,7 @@
init {
delayedInit {
- _inputNoteType = withCol { getNote(noteIds.first()) }.notetype
+ inputNoteType = withCol { getNote(noteIds.first()) }.notetype
availableNoteTypes = withCol { notetypes.all().sortedWith(NamedJSONComparator.INSTANCE) }
// delayed init of outputNoteType and dependent properties
AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt
Show resolved
Hide resolved
AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt
Outdated
Show resolved
Hide resolved
AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt
Show resolved
Hide resolved
david-allison
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt
Show resolved
Hide resolved
AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt
Show resolved
Hide resolved
AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt
Outdated
Show resolved
Hide resolved
AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt
Outdated
Show resolved
Hide resolved
|
Fixed! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces a new "Change Note Type" dialog feature that allows users to bulk-change the note type of multiple notes, with field and template mapping capabilities. The implementation includes a comprehensive ViewModel, Dialog UI with tabs for field and template mapping, and integration with the Card Browser. This feature is currently gated behind a developer preference flag as it's work-in-progress.
Key changes:
- New
ChangeNoteTypeViewModelandChangeNoteTypeDialogfor managing note type conversions with field/template mapping - Integration with Card Browser via menu item and keyboard shortcut (Ctrl+Shift+M)
- Comprehensive test coverage with 20+ test cases covering various conversion scenarios (regular↔cloze, field/template mapping)
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt | Core ViewModel implementing note type change logic with field/template mapping |
| AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt | Dialog UI with tabs for field and template selection |
| AnkiDroid/src/test/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModelTest.kt | Comprehensive test suite covering conversion scenarios |
| AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserViewModel.kt | Added requestChangeNoteType() and validation logic |
| AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt | Menu integration and keyboard shortcut for change note type |
| AnkiDroid/src/main/java/com/ichi2/anki/utils/ViewModelUtils.kt | New ViewModelDelayedInitializer interface for async ViewModel initialization |
| AnkiDroid/src/main/res/layout/*.xml | Three new layouts for the dialog (main, fields tab, templates tab) |
| libanki/testutils/src/main/java/com/ichi2/anki/libanki/testutils/AnkiTest.kt | Added test utilities for creating cloze note types and notes with template counts |
| libanki/src/main/java/com/ichi2/anki/libanki/Notetypes.kt | Deprecated change() method with reference to new dialog |
| AnkiDroid/src/main/java/com/ichi2/utils/*.kt | New utility functions for bold lists and list separators |
| common/src/main/java/com/ichi2/anki/common/json/JSONContainer.kt | Added size property and indices property for convenience |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserViewModel.kt
Outdated
Show resolved
Hide resolved
AnkiDroid/src/test/java/com/ichi2/anki/browser/CardBrowserViewModelTest.kt
Outdated
Show resolved
Hide resolved
AnkiDroid/src/test/java/com/ichi2/anki/browser/CardBrowserViewModelTest.kt
Outdated
Show resolved
Hide resolved
AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeViewModel.kt
Show resolved
Hide resolved
1f914bc to
b8a40cd
Compare
david-allison
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, I feel this can go live immediately once we cut for 2.24
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
|
Nothing much, just a commit message fix |


Purpose / Description
Introduces the
changeNoteTypeDialogandchangeNoteTypeViewModelFixes
How Has This Been Tested?
Pixel 9 Pro (Android 1
Checklist
Please, go through these checks before submitting the PR.