Skip to content

Commit 66ec083

Browse files
committed
Introduce NoteEditorActivity to host the NoteEditorFragment
1 parent a4de2fa commit 66ec083

File tree

9 files changed

+264
-135
lines changed

9 files changed

+264
-135
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: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
intent.extras?.let { bundle ->
63+
// Convert intent extras to NoteEditorLauncher
64+
NoteEditorLauncher.PassArguments(bundle)
65+
} ?: NoteEditorLauncher.AddNote()
66+
67+
supportFragmentManager.commit {
68+
replace(R.id.note_editor_fragment_frame, NoteEditorFragment.newInstance(launcher))
69+
}
70+
71+
startLoadingCollection()
72+
}
73+
74+
/**
75+
* Retrieves the [NoteEditorFragment]
76+
*/
77+
val noteEditorFragment: NoteEditorFragment?
78+
get() = supportFragmentManager.findFragmentById(R.id.note_editor_fragment_frame) as? NoteEditorFragment
79+
80+
override fun onCollectionLoaded(col: Collection) {
81+
super.onCollectionLoaded(col)
82+
Timber.d("onCollectionLoaded()")
83+
registerReceiver()
84+
}
85+
86+
override fun onDeckSelected(deck: DeckSelectionDialog.SelectableDeck?) {
87+
noteEditorFragment?.onDeckSelected(deck)
88+
}
89+
90+
override val subtitleText: String
91+
get() = noteEditorFragment?.subtitleText ?: ""
92+
93+
override fun onSelectedTags(
94+
selectedTags: List<String>,
95+
indeterminateTags: List<String>,
96+
stateFilter: CardStateFilter,
97+
) {
98+
noteEditorFragment?.onSelectedTags(selectedTags, indeterminateTags, stateFilter)
99+
}
100+
101+
override fun dispatchKeyEvent(event: android.view.KeyEvent): Boolean =
102+
noteEditorFragment?.dispatchKeyEvent(event) ?: false || super.dispatchKeyEvent(event)
103+
104+
override val shortcuts: ShortcutGroup
105+
get() = noteEditorFragment?.shortcuts ?: ShortcutGroup(emptyList(), 0)
106+
}

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

Lines changed: 13 additions & 2 deletions
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
@@ -551,7 +556,7 @@ class NoteEditorFragment :
551556

552557
// Hide mainToolbar since CardBrowser handles the toolbar in fragmented activities.
553558
if (inFragmentedActivity) {
554-
mainToolbar.visibility = View.GONE
559+
mainToolbar?.visibility = View.GONE
555560
}
556561

557562
try {
@@ -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)