Skip to content

Commit 9567ffc

Browse files
committed
Introduce NoteEditorActivity to host the NoteEditorFragment
1 parent a4de2fa commit 9567ffc

File tree

10 files changed

+293
-140
lines changed

10 files changed

+293
-140
lines changed

AnkiDroid/src/androidTest/java/com/ichi2/anki/NoteEditorIntentTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class NoteEditorIntentTest : InstrumentedTest() {
4242
var runtimePermissionRule: TestRule? = GrantStoragePermission.instance
4343

4444
@get:Rule
45-
var activityRuleIntent: ActivityScenarioRule<SingleFragmentActivity>? =
45+
var activityRuleIntent: ActivityScenarioRule<NoteEditorActivity>? =
4646
ActivityScenarioRule(
4747
noteEditorTextIntent,
4848
)
@@ -57,7 +57,7 @@ class NoteEditorIntentTest : InstrumentedTest() {
5757
var currentFieldStrings: String? = null
5858
scenario.onActivity { activity ->
5959
val editor = activity.getEditor()
60-
currentFieldStrings = editor.currentFieldStrings[0]
60+
currentFieldStrings = editor!!.currentFieldStrings[0]
6161
}
6262
MatcherAssert.assertThat(currentFieldStrings!!, Matchers.equalTo("sample text"))
6363
}

AnkiDroid/src/androidTest/java/com/ichi2/anki/NoteEditorTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ abstract class NoteEditorTest protected constructor() {
4545
.takeUnless { isInvalid }
4646

4747
@get:Rule
48-
var activityRule: ActivityScenarioRule<SingleFragmentActivity>? =
49-
ActivityScenarioRule<SingleFragmentActivity>(
48+
var activityRule: ActivityScenarioRule<NoteEditorActivity>? =
49+
ActivityScenarioRule<NoteEditorActivity>(
5050
noteEditorIntent,
5151
).takeUnless { isInvalid }
5252

AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/NoteEditorFragment.kt

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
package com.ichi2.anki.testutil
1818

1919
import androidx.test.core.app.ActivityScenario
20+
import com.ichi2.anki.NoteEditorActivity
2021
import com.ichi2.anki.NoteEditorFragment
2122
import com.ichi2.anki.R
22-
import com.ichi2.anki.SingleFragmentActivity
2323
import java.util.concurrent.atomic.AtomicReference
2424

2525
/**
@@ -28,11 +28,11 @@ import java.util.concurrent.atomic.AtomicReference
2828
* @throws Throwable if any exception is thrown during the execution of the block.
2929
*/
3030
@Throws(Throwable::class)
31-
fun ActivityScenario<SingleFragmentActivity>.onNoteEditor(block: (NoteEditorFragment) -> Unit) {
31+
fun ActivityScenario<NoteEditorActivity>.onNoteEditor(block: (NoteEditorFragment) -> Unit) {
3232
val wrapped = AtomicReference<Throwable?>(null)
33-
this.onActivity { activity: SingleFragmentActivity ->
33+
this.onActivity { activity: NoteEditorActivity ->
3434
try {
35-
val editor = activity.getEditor()
35+
val editor: NoteEditorFragment = activity.getEditor()!!
3636
activity.runOnUiThread {
3737
try {
3838
block(editor)
@@ -47,8 +47,5 @@ fun ActivityScenario<SingleFragmentActivity>.onNoteEditor(block: (NoteEditorFrag
4747
wrapped.get()?.let { throw it }
4848
}
4949

50-
/**
51-
* Extension function for SingleFragmentActivity to find the NoteEditor fragment
52-
*/
53-
fun SingleFragmentActivity.getEditor(): NoteEditorFragment =
54-
supportFragmentManager.findFragmentById(R.id.fragment_container) as NoteEditorFragment
50+
fun NoteEditorActivity.getEditor(): NoteEditorFragment? =
51+
supportFragmentManager.findFragmentById(R.id.note_editor_fragment_frame) as NoteEditorFragment

AnkiDroid/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,12 @@
452452
android:exported="false"
453453
android:configChanges="orientation|screenSize"
454454
/>
455+
<activity
456+
android:name="com.ichi2.anki.NoteEditorActivity"
457+
android:exported="false"
458+
android:configChanges="keyboardHidden|orientation|screenSize"
459+
android:windowSoftInputMode="stateAlwaysHidden|adjustResize"
460+
/>
455461
<activity
456462
android:name="com.ichi2.anki.previewer.CardViewerActivity"
457463
android:exported="false"
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright (c) 2025 Hari Srinivasan <harisrini21@gmail.com>
3+
*
4+
* This program is free software; you can redistribute it and/or modify it under
5+
* the terms of the GNU General Public License as published by the Free Software
6+
* Foundation; either version 3 of the License, or (at your option) any later
7+
* version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
10+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11+
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License along with
14+
* this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
package com.ichi2.anki
18+
19+
import android.os.Bundle
20+
import androidx.fragment.app.commit
21+
import com.ichi2.anki.android.input.ShortcutGroup
22+
import com.ichi2.anki.android.input.ShortcutGroupProvider
23+
import com.ichi2.anki.dialogs.DeckSelectionDialog
24+
import com.ichi2.anki.dialogs.DeckSelectionDialog.DeckSelectionListener
25+
import com.ichi2.anki.dialogs.tags.TagsDialogListener
26+
import com.ichi2.anki.libanki.Collection
27+
import com.ichi2.anki.model.CardStateFilter
28+
import com.ichi2.anki.noteeditor.NoteEditorLauncher
29+
import com.ichi2.anki.snackbar.BaseSnackbarBuilderProvider
30+
import com.ichi2.anki.snackbar.SnackbarBuilder
31+
import com.ichi2.anki.widgets.DeckDropDownAdapter.SubtitleListener
32+
import timber.log.Timber
33+
34+
/**
35+
* To find the actual note Editor, @see [NoteEditorFragment]
36+
* This activity contains the NoteEditorFragment, and, on x-large screens, the previewer fragment.
37+
* It also ensures that changes in the note are transmitted to the previewer
38+
*/
39+
class NoteEditorActivity :
40+
AnkiActivity(),
41+
DeckSelectionListener,
42+
SubtitleListener,
43+
TagsDialogListener,
44+
BaseSnackbarBuilderProvider,
45+
DispatchKeyEventListener,
46+
ShortcutGroupProvider {
47+
override val baseSnackbarBuilder: SnackbarBuilder = { }
48+
49+
override fun onCreate(savedInstanceState: Bundle?) {
50+
if (showedActivityFailedScreen(savedInstanceState)) {
51+
return
52+
}
53+
super.onCreate(savedInstanceState)
54+
if (!ensureStoragePermissions()) {
55+
return
56+
}
57+
58+
setContentView(R.layout.note_editor)
59+
60+
// Create and launch the NoteEditorFragment based on the launcher intent
61+
val launcher =
62+
if (intent.hasExtra(FRAGMENT_NAME_EXTRA)) {
63+
val fragmentClassName = intent.getStringExtra(FRAGMENT_NAME_EXTRA)
64+
if (fragmentClassName == NoteEditorFragment::class.java.name) {
65+
val fragmentArgs = intent.getBundleExtra(FRAGMENT_ARGS_EXTRA)
66+
if (fragmentArgs != null) {
67+
NoteEditorLauncher.PassArguments(fragmentArgs)
68+
} else {
69+
NoteEditorLauncher.AddNote()
70+
}
71+
} else {
72+
NoteEditorLauncher.AddNote()
73+
}
74+
} else {
75+
// Regular NoteEditorActivity intent handling
76+
intent.getBundleExtra(FRAGMENT_ARGS_EXTRA)?.let { fragmentArgs ->
77+
// If FRAGMENT_ARGS_EXTRA is provided, use it directly
78+
NoteEditorLauncher.PassArguments(fragmentArgs)
79+
} ?: intent.extras?.let { bundle ->
80+
// Otherwise, use the general extras bundle
81+
NoteEditorLauncher.PassArguments(bundle)
82+
} ?: NoteEditorLauncher.AddNote()
83+
}
84+
85+
supportFragmentManager.commit {
86+
replace(R.id.note_editor_fragment_frame, NoteEditorFragment.newInstance(launcher), FRAGMENT_TAG)
87+
}
88+
89+
startLoadingCollection()
90+
}
91+
92+
/**
93+
* Retrieves the [NoteEditorFragment]
94+
*/
95+
val noteEditorFragment: NoteEditorFragment?
96+
get() = supportFragmentManager.findFragmentById(R.id.note_editor_fragment_frame) as? NoteEditorFragment
97+
98+
override fun onCollectionLoaded(col: Collection) {
99+
super.onCollectionLoaded(col)
100+
Timber.d("onCollectionLoaded()")
101+
registerReceiver()
102+
}
103+
104+
override fun onDeckSelected(deck: DeckSelectionDialog.SelectableDeck?) {
105+
noteEditorFragment?.onDeckSelected(deck)
106+
}
107+
108+
override val subtitleText: String
109+
get() = noteEditorFragment?.subtitleText ?: ""
110+
111+
override fun onSelectedTags(
112+
selectedTags: List<String>,
113+
indeterminateTags: List<String>,
114+
stateFilter: CardStateFilter,
115+
) {
116+
noteEditorFragment?.onSelectedTags(selectedTags, indeterminateTags, stateFilter)
117+
}
118+
119+
override fun dispatchKeyEvent(event: android.view.KeyEvent): Boolean =
120+
noteEditorFragment?.dispatchKeyEvent(event) ?: false || super.dispatchKeyEvent(event)
121+
122+
override val shortcuts: ShortcutGroup
123+
get() = noteEditorFragment?.shortcuts ?: ShortcutGroup(emptyList(), 0)
124+
125+
companion object {
126+
const val FRAGMENT_ARGS_EXTRA = "fragment_args"
127+
const val FRAGMENT_NAME_EXTRA = "fragmentName"
128+
const val FRAGMENT_TAG = "NoteEditorFragmentTag"
129+
}
130+
}

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,15 @@ import androidx.core.os.BundleCompat
7070
import androidx.core.text.HtmlCompat
7171
import androidx.core.util.component1
7272
import androidx.core.util.component2
73+
import androidx.core.view.MenuHost
7374
import androidx.core.view.MenuProvider
7475
import androidx.core.view.OnReceiveContentListener
7576
import androidx.core.view.WindowInsetsControllerCompat
7677
import androidx.core.view.isVisible
7778
import androidx.draganddrop.DropHelper
7879
import androidx.fragment.app.Fragment
7980
import androidx.fragment.app.activityViewModels
81+
import androidx.lifecycle.Lifecycle
8082
import androidx.lifecycle.lifecycleScope
8183
import anki.config.ConfigKey
8284
import anki.notes.NoteFieldsCheckResponse
@@ -226,8 +228,11 @@ class NoteEditorFragment :
226228
private val getColUnsafe: Collection
227229
get() = CollectionManager.getColUnsafe()
228230

231+
private val noteEditorActivity: NoteEditorActivity
232+
get() = requireActivity() as NoteEditorActivity
233+
229234
private val mainToolbar: androidx.appcompat.widget.Toolbar
230-
get() = requireView().findViewById(R.id.toolbar)
235+
get() = noteEditorActivity.findViewById(R.id.toolbar)
231236

232237
/**
233238
* Flag which forces the calling activity to rebuild it's definition of current card from scratch
@@ -578,7 +583,13 @@ class NoteEditorFragment :
578583
requireActivity().onBackPressedDispatcher.onBackPressed()
579584
}
580585

586+
// Register this fragment as a menu provider with the activity
581587
mainToolbar.addMenuProvider(this)
588+
(requireActivity() as MenuHost).addMenuProvider(
589+
this,
590+
viewLifecycleOwner,
591+
Lifecycle.State.RESUMED,
592+
)
582593
}
583594

584595
/**

AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/NoteEditorLauncher.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ import android.os.Parcelable
2424
import androidx.core.os.bundleOf
2525
import com.ichi2.anim.ActivityTransitionAnimation
2626
import com.ichi2.anki.AnkiActivity
27+
import com.ichi2.anki.NoteEditorActivity
2728
import com.ichi2.anki.NoteEditorFragment
2829
import com.ichi2.anki.NoteEditorFragment.Companion.NoteEditorCaller
29-
import com.ichi2.anki.SingleFragmentActivity
3030
import com.ichi2.anki.browser.CardBrowserViewModel
3131
import com.ichi2.anki.libanki.CardId
3232
import com.ichi2.anki.libanki.DeckId
@@ -39,16 +39,19 @@ sealed interface NoteEditorLauncher : Destination {
3939
override fun toIntent(context: Context): Intent = toIntent(context, action = null)
4040

4141
/**
42-
* Generates an intent to open the NoteEditor fragment with the configured parameters.
42+
* Generates an intent to open the NoteEditor activity with the configured parameters
4343
*
4444
* @param context The context from which the intent is launched.
4545
* @param action Optional action string for the intent.
46-
* @return Intent configured to launch the NoteEditor fragment.
46+
* @return Intent configured to launch the appropriate activity.
4747
*/
4848
fun toIntent(
4949
context: Context,
5050
action: String? = null,
51-
) = SingleFragmentActivity.getIntent(context, NoteEditorFragment::class, toBundle(), action)
51+
) = Intent(context, NoteEditorActivity::class.java).apply {
52+
putExtras(toBundle())
53+
action?.let { this.action = it }
54+
}
5255

5356
/**
5457
* Converts the configuration into a Bundle to pass arguments to the NoteEditor fragment.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:layout_width="match_parent"
4+
android:layout_height="match_parent">
5+
<LinearLayout
6+
android:layout_width="match_parent"
7+
android:layout_height="match_parent"
8+
android:orientation="vertical">
9+
<include layout="@layout/toolbar" />
10+
<androidx.fragment.app.FragmentContainerView
11+
android:id="@+id/note_editor_fragment_frame"
12+
android:layout_width="match_parent"
13+
android:layout_height="match_parent" />
14+
</LinearLayout>
15+
</androidx.coordinatorlayout.widget.CoordinatorLayout>

0 commit comments

Comments
 (0)