Skip to content

Commit 156ee76

Browse files
committed
Refactor NoteEditor to use Activity
1 parent 20e63a7 commit 156ee76

File tree

10 files changed

+338
-135
lines changed

10 files changed

+338
-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: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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 android.view.Menu
21+
import android.view.MenuItem
22+
import android.view.View
23+
import androidx.annotation.IdRes
24+
import androidx.core.view.MenuProvider
25+
import androidx.fragment.app.commit
26+
import com.ichi2.anki.NoteEditorFragment.NoteEditorHost
27+
import com.ichi2.anki.android.input.ShortcutGroup
28+
import com.ichi2.anki.android.input.ShortcutGroupProvider
29+
import com.ichi2.anki.dialogs.DeckSelectionDialog
30+
import com.ichi2.anki.dialogs.DeckSelectionDialog.DeckSelectionListener
31+
import com.ichi2.anki.dialogs.tags.TagsDialogListener
32+
import com.ichi2.anki.libanki.Collection
33+
import com.ichi2.anki.model.CardStateFilter
34+
import com.ichi2.anki.noteeditor.NoteEditorLauncher
35+
import com.ichi2.anki.snackbar.BaseSnackbarBuilderProvider
36+
import com.ichi2.anki.snackbar.SnackbarBuilder
37+
import com.ichi2.anki.widgets.DeckDropDownAdapter.SubtitleListener
38+
import timber.log.Timber
39+
40+
/**
41+
* To find the actual note Editor, @see [NoteEditorFragment]
42+
* This activity contains the NoteEditorFragment, and, on x-large screens, the previewer fragment.
43+
* It also ensures that changes in the note are transmitted to the previewer
44+
*/
45+
class NoteEditorActivity :
46+
AnkiActivity(),
47+
DeckSelectionListener,
48+
SubtitleListener,
49+
TagsDialogListener,
50+
BaseSnackbarBuilderProvider,
51+
DispatchKeyEventListener,
52+
MenuProvider,
53+
ShortcutGroupProvider,
54+
NoteEditorHost {
55+
override val baseSnackbarBuilder: SnackbarBuilder = { }
56+
57+
override fun findViewInHost(
58+
@IdRes id: Int,
59+
): View = findViewById(id)
60+
61+
override fun onCreate(savedInstanceState: Bundle?) {
62+
if (showedActivityFailedScreen(savedInstanceState)) {
63+
return
64+
}
65+
super.onCreate(savedInstanceState)
66+
if (!ensureStoragePermissions()) {
67+
return
68+
}
69+
70+
setContentView(R.layout.note_editor)
71+
72+
// Create and launch the NoteEditorFragment based on the launcher intent
73+
val launcher =
74+
intent.extras?.let { bundle ->
75+
// Convert intent extras to NoteEditorLauncher
76+
NoteEditorLauncher.PassArguments(bundle)
77+
} ?: NoteEditorLauncher.AddNote()
78+
79+
supportFragmentManager.commit {
80+
replace(R.id.note_editor_fragment_frame, NoteEditorFragment.newInstance(launcher))
81+
}
82+
83+
startLoadingCollection()
84+
}
85+
86+
/**
87+
* Retrieves the [NoteEditorFragment]
88+
*/
89+
val noteEditorFragment: NoteEditorFragment?
90+
get() = supportFragmentManager.findFragmentById(R.id.note_editor_fragment_frame) as? NoteEditorFragment
91+
92+
override fun onCollectionLoaded(col: Collection) {
93+
super.onCollectionLoaded(col)
94+
Timber.d("onCollectionLoaded()")
95+
registerReceiver()
96+
}
97+
98+
override fun onDeckSelected(deck: DeckSelectionDialog.SelectableDeck?) {
99+
noteEditorFragment?.onDeckSelected(deck)
100+
}
101+
102+
override val subtitleText: String
103+
get() = noteEditorFragment?.subtitleText ?: ""
104+
105+
override fun onSelectedTags(
106+
selectedTags: List<String>,
107+
indeterminateTags: List<String>,
108+
stateFilter: CardStateFilter,
109+
) {
110+
noteEditorFragment?.onSelectedTags(selectedTags, indeterminateTags, stateFilter)
111+
}
112+
113+
override fun dispatchKeyEvent(event: android.view.KeyEvent): Boolean =
114+
noteEditorFragment?.dispatchKeyEvent(event) ?: false || super.dispatchKeyEvent(event)
115+
116+
override fun onCreateMenu(
117+
menu: Menu,
118+
menuInflater: android.view.MenuInflater,
119+
) {
120+
noteEditorFragment?.onCreateMenu(menu, menuInflater)
121+
}
122+
123+
override fun onMenuItemSelected(item: MenuItem): Boolean = noteEditorFragment?.onMenuItemSelected(item) ?: false
124+
125+
override val shortcuts: ShortcutGroup
126+
get() = noteEditorFragment?.shortcuts ?: ShortcutGroup(emptyList(), 0)
127+
}

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import androidx.activity.result.ActivityResultCallback
5656
import androidx.activity.result.contract.ActivityResultContracts
5757
import androidx.annotation.CheckResult
5858
import androidx.annotation.DrawableRes
59+
import androidx.annotation.IdRes
5960
import androidx.annotation.VisibleForTesting
6061
import androidx.appcompat.app.AlertDialog
6162
import androidx.appcompat.app.AppCompatActivity
@@ -226,8 +227,17 @@ class NoteEditorFragment :
226227
private val getColUnsafe: Collection
227228
get() = CollectionManager.getColUnsafe()
228229

230+
interface NoteEditorHost {
231+
fun findViewInHost(
232+
@IdRes id: Int,
233+
): View
234+
}
235+
236+
private val noteEditorHost: NoteEditorHost
237+
get() = activity as NoteEditorHost
238+
229239
private val mainToolbar: androidx.appcompat.widget.Toolbar
230-
get() = requireView().findViewById(R.id.toolbar)
240+
get() = noteEditorHost.findViewInHost(R.id.toolbar) as androidx.appcompat.widget.Toolbar
231241

232242
/**
233243
* Flag which forces the calling activity to rebuild it's definition of current card from scratch
@@ -551,7 +561,7 @@ class NoteEditorFragment :
551561

552562
// Hide mainToolbar since CardBrowser handles the toolbar in fragmented activities.
553563
if (inFragmentedActivity) {
554-
mainToolbar.visibility = View.GONE
564+
mainToolbar?.visibility = View.GONE
555565
}
556566

557567
try {

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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent">
6+
<LinearLayout
7+
android:layout_width="match_parent"
8+
android:layout_height="match_parent"
9+
android:orientation="vertical">
10+
<include layout="@layout/toolbar" />
11+
<LinearLayout
12+
android:id="@+id/note_editor_xl_view"
13+
android:layout_width="match_parent"
14+
android:layout_height="match_parent"
15+
android:background="?android:attr/colorBackground"
16+
android:orientation="horizontal">
17+
<androidx.fragment.app.FragmentContainerView
18+
android:id="@+id/note_editor_fragment_frame"
19+
android:layout_width="1dip"
20+
android:layout_weight="1"
21+
android:layout_height="match_parent"/>
22+
<FrameLayout
23+
android:id="@+id/note_editor_resizing_divider"
24+
android:layout_width="8dp"
25+
android:layout_height="match_parent"
26+
android:background="@color/idle_divider_color">
27+
<View
28+
android:id="@+id/note_editor_divider_handle"
29+
android:layout_width="3dp"
30+
android:layout_height="30dp"
31+
android:layout_gravity="center"
32+
android:background="@drawable/divider_handle_background" />
33+
</FrameLayout>
34+
<LinearLayout
35+
android:id="@+id/previewer_frame_layout"
36+
android:layout_width="1dip"
37+
android:layout_height="match_parent"
38+
android:orientation="vertical"
39+
android:layout_weight="1">
40+
<com.google.android.material.tabs.TabLayout
41+
android:id="@+id/previewer_tab_layout"
42+
android:background="@color/transparent"
43+
android:layout_width="match_parent"
44+
android:layout_height="wrap_content"
45+
app:tabMode="scrollable"
46+
style="@style/Widget.Material3.TabLayout" />
47+
<androidx.fragment.app.FragmentContainerView
48+
android:id="@+id/previewer_frame"
49+
android:layout_width="match_parent"
50+
android:layout_height="match_parent"/>
51+
</LinearLayout>
52+
</LinearLayout>
53+
</LinearLayout>
54+
</androidx.coordinatorlayout.widget.CoordinatorLayout>
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)