From f5eedc8ba5d9506b14c8f9a08fd22b555305abf2 Mon Sep 17 00:00:00 2001 From: Rohanraj123 Date: Tue, 6 Aug 2024 05:18:55 +0530 Subject: [PATCH 1/9] test: Added test case for requireLong() method at BundleUtils.kt --- .../java/com/ichi2/utils/BundleUtilsTest.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/BundleUtilsTest.kt b/AnkiDroid/src/test/java/com/ichi2/utils/BundleUtilsTest.kt index 5edf93dda523..472b2efbe974 100644 --- a/AnkiDroid/src/test/java/com/ichi2/utils/BundleUtilsTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/utils/BundleUtilsTest.kt @@ -17,6 +17,7 @@ package com.ichi2.utils import android.os.Bundle import com.ichi2.utils.BundleUtils.getNullableLong +import com.ichi2.utils.BundleUtils.requireLong import org.junit.Assert.assertEquals import org.junit.Test import org.mockito.Mockito.anyString @@ -26,6 +27,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.kotlin.whenever import kotlin.random.Random +import kotlin.test.assertFailsWith import kotlin.test.assertNull class BundleUtilsTest { @@ -58,6 +60,33 @@ class BundleUtilsTest { assertEquals(expected, value) } + @Test + fun test_RequireLong_NotFound_ThrowsException() { + val mockedBundle = mock(Bundle::class.java) + + whenever(mockedBundle.containsKey(anyString())).thenReturn(false) + + assertFailsWith { mockedBundle.requireLong(KEY) } + + verify(mockedBundle).containsKey(eq(KEY)) + } + + @Test + fun test_RequireLong_Found_ReturnIt() { + val expected = Random.Default.nextLong() + val mockedBundle = mock(Bundle::class.java) + + whenever(mockedBundle.containsKey(anyString())).thenReturn(true) + whenever(mockedBundle.getLong(anyString())).thenReturn(expected) + + val value = mockedBundle.requireLong(KEY) + + verify(mockedBundle).containsKey(eq(KEY)) + verify(mockedBundle).getLong(eq(KEY)) + + assertEquals(expected, value) + } + companion object { const val KEY = "KEY" } From 7512a8e545e46bd5b4dcf198133ddea592311c4a Mon Sep 17 00:00:00 2001 From: Shruti Gitte Date: Tue, 23 Jul 2024 01:50:23 +0530 Subject: [PATCH 2/9] Fix:Status bar color --- AnkiDroid/src/main/java/com/ichi2/anki/AnkiFragment.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiFragment.kt index 1c34f059cd9b..9f36fcc922bd 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiFragment.kt @@ -17,7 +17,6 @@ package com.ichi2.anki import android.content.BroadcastReceiver import android.net.Uri -import android.os.Bundle import android.view.Menu import android.view.View import androidx.annotation.AttrRes @@ -67,11 +66,6 @@ open class AnkiFragment(@LayoutRes layout: Int) : Fragment(layout) { hideProgressBar() } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - requireActivity().window.statusBarColor = ThemeUtils.getThemeAttrColor(requireContext(), R.attr.appBarColor) - super.onViewCreated(view, savedInstanceState) - } - // Helper functions: These make fragment code shorter /** From edb562a3bc2144a29841d463200ac9ad689dda65 Mon Sep 17 00:00:00 2001 From: SanjaySargam Date: Thu, 30 May 2024 11:41:17 +0530 Subject: [PATCH 3/9] Attach TemplatePreviewerFragment to CardTemplateEditor On xlarge screen, display the previewer on the trailing side of the card template editor This commit introduces a new view, card_template_editor.xml, that contains the card template editor (implemented in card_template_editor_activity.xml) and potentially the previewer on xlarge screen. CardTemplateEditor.kt simply set the content view to card_template_editor instead of card_template_editor_activity, and add the previewer if needed. --- .../java/com/ichi2/anki/CardTemplateEditor.kt | 55 ++++++++++++++++++- .../previewer/TemplatePreviewerFragment.kt | 6 ++ .../layout-xlarge/card_template_editor.xml | 22 ++++++++ .../main/res/layout/card_template_editor.xml | 6 ++ 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 AnkiDroid/src/main/res/layout-xlarge/card_template_editor.xml create mode 100644 AnkiDroid/src/main/res/layout/card_template_editor.xml diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt index e7ef36880e10..039d88163c9a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt @@ -42,6 +42,8 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentContainerView +import androidx.fragment.app.commit import androidx.lifecycle.Lifecycle import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 @@ -107,6 +109,17 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { private var tabToViewId: HashMap = HashMap() private var startingOrdId = 0 + /** + * The frame containing the template previewer. Non null only in layout x-large. + */ + private var templatePreviewerFrame: FragmentContainerView? = null + + /** + * If true, the view is split in two. The template editor appears on the leading side and the previewer on the trailing side. + * This occurs when the view is big enough. + */ + private var fragmented = false + // ---------------------------------------------------------------------------- // Listeners // ---------------------------------------------------------------------------- @@ -118,7 +131,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { return } super.onCreate(savedInstanceState) - setContentView(R.layout.card_template_editor_activity) + setContentView(R.layout.card_template_editor) // Load the args either from the intent or savedInstanceState bundle if (savedInstanceState == null) { // get model id @@ -143,6 +156,14 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { tempModel = CardTemplateNotetype.fromBundle(savedInstanceState) } + templatePreviewerFrame = findViewById(R.id.template_previewer_fragment) + /** + * Check if templatePreviewerFrame is not null and if its visibility is set to VISIBLE. + * If both conditions are true, assign true to the variable [fragmented], otherwise assign false. + * [fragmented] will be true if the screen size is large otherwise false + */ + fragmented = templatePreviewerFrame != null && templatePreviewerFrame?.visibility == View.VISIBLE + slidingTabLayout = findViewById(R.id.sliding_tabs) viewPager = findViewById(R.id.card_template_editor_pager) setNavigationBarColor(R.attr.appBarColor) @@ -150,6 +171,36 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { // Disable the home icon enableToolbar() startLoadingCollection() + + // Open TemplatePreviewerFragment if in fragmented mode + loadTemplatePreviewerFragmentIfFragmented() + } + + /** + * Loads or reloads [tempModel] in [R.id.template_previewer_fragment] if the view is fragmented. Do nothing otherwise. + */ + private fun loadTemplatePreviewerFragmentIfFragmented() { + if (!fragmented) { + return + } + launchCatchingTask { + val notetype = tempModel!!.notetype + val notetypeFile = NotetypeFile(this@CardTemplateEditor, notetype) + val ord = viewPager.currentItem + val note = withCol { currentFragment?.getNote(this) ?: Note.fromNotetypeId(notetype.id) } + val args = TemplatePreviewerArguments( + notetypeFile = notetypeFile, + id = note.id, + ord = ord, + fields = note.fields, + tags = note.tags, + fillEmpty = true + ) + val details = TemplatePreviewerFragment.newInstance(args) + supportFragmentManager.commit { + replace(R.id.template_previewer_fragment, details) + } + } } public override fun onSaveInstanceState(outState: Bundle) { @@ -778,7 +829,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { templateEditor.finish() } - private fun getNote(col: Collection): Note? { + fun getNote(col: Collection): Note? { val nid = requireArguments().getLong(EDITOR_NOTE_ID) return if (nid != -1L) col.getNote(nid) else null } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt index bfb43b9aea48..c586d8e61c61 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt @@ -113,6 +113,12 @@ class TemplatePreviewerFragment : companion object { const val ARGS_KEY = "templatePreviewerArgs" + fun newInstance(arguments: TemplatePreviewerArguments): TemplatePreviewerFragment { + return TemplatePreviewerFragment().apply { + this.arguments = bundleOf(ARGS_KEY to arguments) + } + } + fun getIntent(context: Context, arguments: TemplatePreviewerArguments): Intent { return CardViewerActivity.getIntent( context, diff --git a/AnkiDroid/src/main/res/layout-xlarge/card_template_editor.xml b/AnkiDroid/src/main/res/layout-xlarge/card_template_editor.xml new file mode 100644 index 000000000000..d981dc759f97 --- /dev/null +++ b/AnkiDroid/src/main/res/layout-xlarge/card_template_editor.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/AnkiDroid/src/main/res/layout/card_template_editor.xml b/AnkiDroid/src/main/res/layout/card_template_editor.xml new file mode 100644 index 000000000000..3936d7ad04b8 --- /dev/null +++ b/AnkiDroid/src/main/res/layout/card_template_editor.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file From c0405e5fb314aea250bb7aa2933c765251ce0446 Mon Sep 17 00:00:00 2001 From: SanjaySargam Date: Thu, 30 May 2024 13:45:23 +0530 Subject: [PATCH 4/9] Merge previewer's back button into the editor's one when the view is fragmented. This commit adds a fragmented variable to TemplatePreviewerArguments, to let the previewer knows whether it belongs to a fragmented view. If so, the previewer's back button is removed, hence only the editor's back button remain. --- .../java/com/ichi2/anki/CardTemplateEditor.kt | 3 ++- .../previewer/TemplatePreviewerFragment.kt | 23 +++++++++++++++---- .../previewer/TemplatePreviewerViewModel.kt | 3 ++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt index 039d88163c9a..49629f68e359 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt @@ -194,7 +194,8 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { ord = ord, fields = note.fields, tags = note.tags, - fillEmpty = true + fillEmpty = true, + inFragmentedActivity = fragmented ) val details = TemplatePreviewerFragment.newInstance(args) supportFragmentManager.commit { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt index c586d8e61c61..ca39a609580a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt @@ -44,10 +44,16 @@ import timber.log.Timber class TemplatePreviewerFragment : CardViewerFragment(R.layout.template_previewer), BaseSnackbarBuilderProvider { + /** + * Whether this is displayed in a fragment view. + * If true, this fragment is on the trailing side of the card template editor. + */ + private var inFragmentedActivity = false + private lateinit var templatePreviewerArguments: TemplatePreviewerArguments override val viewModel: TemplatePreviewerViewModel by viewModels { - val arguments = BundleCompat.getParcelable(requireArguments(), ARGS_KEY, TemplatePreviewerArguments::class.java)!! - TemplatePreviewerViewModel.factory(arguments, CardMediaPlayer()) + templatePreviewerArguments = BundleCompat.getParcelable(requireArguments(), ARGS_KEY, TemplatePreviewerArguments::class.java)!! + TemplatePreviewerViewModel.factory(templatePreviewerArguments, CardMediaPlayer()) } override val webView: WebView get() = requireView().findViewById(R.id.webview) @@ -58,8 +64,17 @@ class TemplatePreviewerFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - view.findViewById(R.id.toolbar).setNavigationOnClickListener { - requireActivity().onBackPressedDispatcher.onBackPressed() + val toolbar = view.findViewById(R.id.toolbar) + // Retrieve the boolean argument "inFragmentedActivity" from the fragment's arguments bundle + inFragmentedActivity = templatePreviewerArguments.inFragmentedActivity + // If this fragment is a part of fragmented activity, then there is already a navigation icon an the activity. + // We hide the one specific to this fragment + if (inFragmentedActivity) { + toolbar.navigationIcon = null + } else { + toolbar.setNavigationOnClickListener { + requireActivity().onBackPressedDispatcher.onBackPressed() + } } val showAnswerButton = view.findViewById(R.id.show_answer).apply { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerViewModel.kt index dadb50050da7..20f165886efd 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerViewModel.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerViewModel.kt @@ -237,7 +237,8 @@ data class TemplatePreviewerArguments( val tags: MutableList, val id: Long = 0, val ord: Int = 0, - val fillEmpty: Boolean = false + val fillEmpty: Boolean = false, + val inFragmentedActivity: Boolean = false ) : Parcelable { val notetype: NotetypeJson get() = notetypeFile.getNotetype() } From a256d952bf2ec91765e37b9adfe463efd168737b Mon Sep 17 00:00:00 2001 From: SanjaySargam Date: Sat, 1 Jun 2024 01:20:14 +0530 Subject: [PATCH 5/9] Merge the previewer's menu into the editor's when the view is fragmented This commit switch the previewer menu from MateralToolbar to toolbar so that it has the same menu has the editor. It introduces setupCommonMenu and handleCommonMenuItemSelected that add the previewer's menu entry and handle them. They are called from the editor's menu if the view is fragmented, and from the previewer's own menu otherwise. --- .../java/com/ichi2/anki/CardTemplateEditor.kt | 174 ++++++++++-------- .../previewer/TemplatePreviewerFragment.kt | 18 +- .../main/res/layout/template_previewer.xml | 24 +-- 3 files changed, 116 insertions(+), 100 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt index 49629f68e359..ceacd44dcf4a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt @@ -606,7 +606,9 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { insertField(bundle.getString(InsertFieldDialog.KEY_INSERTED_FIELD)!!) } } - setupMenu() + if (!templateEditor.fragmented) { + setupMenu() + } } private fun initTabLayoutMediator() { @@ -632,92 +634,16 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { } private fun setupMenu() { - // Enable menu (requireActivity() as MenuHost).addMenuProvider( object : MenuProvider { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { menu.clear() menuInflater.inflate(R.menu.card_template_editor, menu) - - if (noteTypeCreatesDynamicNumberOfNotes()) { - Timber.d("Editing cloze/occlusion model, disabling add/delete card template and deck override functionality") - menu.findItem(R.id.action_add).isVisible = false - menu.findItem(R.id.action_rename).isVisible = false - menu.findItem(R.id.action_add_deck_override).isVisible = false - } else { - val template = getCurrentTemplate() - - @StringRes val overrideStringRes = if (template != null && template.has("did") && !template.isNull("did")) { - R.string.card_template_editor_deck_override_on - } else { - R.string.card_template_editor_deck_override_off - } - menu.findItem(R.id.action_add_deck_override).setTitle(overrideStringRes) - } - - // It is invalid to delete if there is only one card template, remove the option from UI - if (templateEditor.tempModel!!.templateCount < 2) { - menu.findItem(R.id.action_delete).isVisible = false - } - - // marked insert field menu item invisible for style view - val isInsertFieldItemVisible = currentEditorViewId != R.id.styling_edit - menu.findItem(R.id.action_insert_field).isVisible = isInsertFieldItemVisible + setupCommonMenu(menu) } override fun onMenuItemSelected(menuItem: MenuItem): Boolean { - return when (menuItem.itemId) { - R.id.action_add -> { - Timber.i("CardTemplateEditor:: Add template button pressed") - addCardTemplate() - return true - } - R.id.action_reposition -> { - showRepositionDialog() - return true - } - R.id.action_rename -> { - Timber.i("CardTemplateEditor:: Rename button pressed") - showRenameDialog() - return true - } - R.id.action_copy_as_markdown -> { - Timber.i("CardTemplateEditor:: Copy markdown button pressed") - copyMarkdownTemplateToClipboard() - return true - } - R.id.action_insert_field -> { - Timber.i("CardTemplateEditor:: Insert field button pressed") - showInsertFieldDialog() - return true - } - R.id.action_delete -> { - Timber.i("CardTemplateEditor:: Delete button pressed") - deleteCardTemplate() - return true - } - R.id.action_add_deck_override -> { - Timber.i("CardTemplateEditor:: Deck override button pressed") - displayDeckOverrideDialog(tempModel) - return true - } - R.id.action_preview -> { - Timber.i("CardTemplateEditor:: Preview button pressed") - performPreview() - return true - } - R.id.action_confirm -> { - Timber.i("CardTemplateEditor:: Save button pressed") - saveNoteType() - } - R.id.action_card_browser_appearance -> { - Timber.i("CardTemplateEditor:: Card browser appearance button pressed") - openBrowserAppearance() - } - else -> { - false - } - } + return handleCommonMenuItemSelected(menuItem) } }, viewLifecycleOwner, @@ -793,6 +719,96 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { return true } + /** + * Setups the part of the menu that can be used either in template editor or in previewer fragment. + */ + fun setupCommonMenu(menu: Menu) { + if (noteTypeCreatesDynamicNumberOfNotes()) { + Timber.d("Editing cloze/occlusion model, disabling add/delete card template and deck override functionality") + menu.findItem(R.id.action_add).isVisible = false + menu.findItem(R.id.action_rename).isVisible = false + menu.findItem(R.id.action_add_deck_override).isVisible = false + } else { + val template = getCurrentTemplate() + + @StringRes val overrideStringRes = if (template != null && template.has("did") && !template.isNull("did")) { + R.string.card_template_editor_deck_override_on + } else { + R.string.card_template_editor_deck_override_off + } + menu.findItem(R.id.action_add_deck_override).setTitle(overrideStringRes) + } + + // It is invalid to delete if there is only one card template, remove the option from UI + if (templateEditor.tempModel!!.templateCount < 2) { + menu.findItem(R.id.action_delete).isVisible = false + } + + // marked insert field menu item invisible for style view + val isInsertFieldItemVisible = currentEditorViewId != R.id.styling_edit + menu.findItem(R.id.action_insert_field).isVisible = isInsertFieldItemVisible + } + + /** + * Handles the part of the menu set by [setupCommonMenu]. + * @returns whether the given item was handled + * @see [onMenuItemSelected] and [onMenuItemClick] + */ + fun handleCommonMenuItemSelected(menuItem: MenuItem): Boolean { + return when (menuItem.itemId) { + R.id.action_add -> { + Timber.i("CardTemplateEditor:: Add template button pressed") + addCardTemplate() + return true + } + R.id.action_reposition -> { + showRepositionDialog() + return true + } + R.id.action_rename -> { + Timber.i("CardTemplateEditor:: Rename button pressed") + showRenameDialog() + return true + } + R.id.action_copy_as_markdown -> { + Timber.i("CardTemplateEditor:: Copy markdown button pressed") + copyMarkdownTemplateToClipboard() + return true + } + R.id.action_insert_field -> { + Timber.i("CardTemplateEditor:: Insert field button pressed") + showInsertFieldDialog() + return true + } + R.id.action_delete -> { + Timber.i("CardTemplateEditor:: Delete template button pressed") + deleteCardTemplate() + return true + } + R.id.action_add_deck_override -> { + Timber.i("CardTemplateEditor:: Deck override button pressed") + displayDeckOverrideDialog(tempModel) + return true + } + R.id.action_preview -> { + Timber.i("CardTemplateEditor:: Preview button pressed") + performPreview() + return true + } + R.id.action_confirm -> { + Timber.i("CardTemplateEditor:: Save model button pressed") + saveNoteType() + } + R.id.action_card_browser_appearance -> { + Timber.i("CardTemplateEditor::Card Browser Template button pressed") + openBrowserAppearance() + } + else -> { + return false + } + } + } + private val currentTemplate: CardTemplate? get() = try { val tempModel = templateEditor.tempModel diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt index ca39a609580a..c0c15e17b3df 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt @@ -18,18 +18,20 @@ package com.ichi2.anki.previewer import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.MenuItem import android.view.View import android.webkit.WebView import androidx.appcompat.widget.ThemeUtils +import androidx.appcompat.widget.Toolbar import androidx.core.os.BundleCompat import androidx.core.os.bundleOf import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope -import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.button.MaterialButton import com.google.android.material.card.MaterialCardView import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout.OnTabSelectedListener +import com.ichi2.anki.CardTemplateEditor import com.ichi2.anki.R import com.ichi2.anki.cardviewer.CardMediaPlayer import com.ichi2.anki.snackbar.BaseSnackbarBuilderProvider @@ -43,7 +45,8 @@ import timber.log.Timber class TemplatePreviewerFragment : CardViewerFragment(R.layout.template_previewer), - BaseSnackbarBuilderProvider { + BaseSnackbarBuilderProvider, + Toolbar.OnMenuItemClickListener { /** * Whether this is displayed in a fragment view. * If true, this fragment is on the trailing side of the card template editor. @@ -64,7 +67,7 @@ class TemplatePreviewerFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val toolbar = view.findViewById(R.id.toolbar) + val toolbar = view.findViewById(R.id.toolbar) // Retrieve the boolean argument "inFragmentedActivity" from the fragment's arguments bundle inFragmentedActivity = templatePreviewerArguments.inFragmentedActivity // If this fragment is a part of fragmented activity, then there is already a navigation icon an the activity. @@ -123,6 +126,15 @@ class TemplatePreviewerFragment : window.navigationBarColor = ThemeUtils.getThemeAttrColor(this, R.attr.alternativeBackgroundColor) } } + if (inFragmentedActivity) { + toolbar.setOnMenuItemClickListener(this) + toolbar.inflateMenu(R.menu.card_template_editor) + (activity as CardTemplateEditor).currentFragment?.setupCommonMenu(toolbar.menu) + } + } + + override fun onMenuItemClick(item: MenuItem): Boolean { + return (activity as CardTemplateEditor).currentFragment?.handleCommonMenuItemSelected(item) == true } companion object { diff --git a/AnkiDroid/src/main/res/layout/template_previewer.xml b/AnkiDroid/src/main/res/layout/template_previewer.xml index ba25dcdf272e..6bc88681d819 100644 --- a/AnkiDroid/src/main/res/layout/template_previewer.xml +++ b/AnkiDroid/src/main/res/layout/template_previewer.xml @@ -19,26 +19,14 @@ android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent"> - - + - - - - - + app:tabMode="scrollable" + /> Date: Sat, 1 Jun 2024 12:45:23 +0530 Subject: [PATCH 6/9] load fragment when performing action on cards This commit updates the previewer when addition, deletion, renaming of cards. --- .../main/java/com/ichi2/anki/CardTemplateEditor.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt index ceacd44dcf4a..1399e36a8429 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt @@ -570,6 +570,8 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { // update the tab templateEditor.viewPager.adapter!!.notifyDataSetChanged() + // Update the tab name in previewer + templateEditor.loadTemplatePreviewerFragmentIfFragmented() } } @@ -1028,12 +1030,21 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { /** * Execute an action on the schema, asking the user to confirm that a full sync is ok + * If [schemaChangingAction] is successfully executed, then the template is reloaded. + * + * This method is always useful because all calls to executeWithSyncCheck may need to refresh the previewer. + * Due to conditional generation (e.g., {{#c5}}foo{{/c5}} which is non-empty only if it's the 5th card and is + * empty otherwise), it's important to reload the template. This is particularly useful for cloze types, + * where a card can move from the 5th to the 6th position due to adding an extra card type, causing content + * to change or be deleted. + * * @param schemaChangingAction The action to execute (adding / removing card) */ private fun executeWithSyncCheck(schemaChangingAction: Runnable) { try { templateEditor.getColUnsafe.modSchema() schemaChangingAction.run() + templateEditor.loadTemplatePreviewerFragmentIfFragmented() } catch (e: ConfirmModSchemaException) { e.log() val d = ConfirmationDialog() From a98bdd5f548ab0fd4f5f25420b8dced123a9f297 Mon Sep 17 00:00:00 2001 From: SanjaySargam Date: Sat, 1 Jun 2024 12:58:05 +0530 Subject: [PATCH 7/9] Change in card type gets reflected in the previewer This commit ensures that if we change card on leading side then the previewer fragment will load and shows same card on trailing side --- .../src/main/java/com/ichi2/anki/CardTemplateEditor.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt index 1399e36a8429..793289806ea9 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt @@ -602,6 +602,15 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { initTabLayoutMediator() + templateEditor.slidingTabLayout?.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(p0: TabLayout.Tab?) { + templateEditor.loadTemplatePreviewerFragmentIfFragmented() + } + override fun onTabUnselected(p0: TabLayout.Tab?) { + } + override fun onTabReselected(p0: TabLayout.Tab?) { + } + }) parentFragmentManager.setFragmentResultListener(REQUEST_FIELD_INSERT, viewLifecycleOwner) { key, bundle -> if (key == REQUEST_FIELD_INSERT) { // this is guaranteed to be non null, as we put a non null value on the other side From b1a94f5ec5a501c227f00b238f7ae4ab17b70b7f Mon Sep 17 00:00:00 2001 From: SanjaySargam Date: Sun, 2 Jun 2024 16:23:59 +0530 Subject: [PATCH 8/9] UI improvements This commit updates the UI to ensure that the previewer theme matches the card template editor theme. CardTemplateEditor - Set navigation bar color to use alternative background color - Update background color attribute TemplatePreviewerFragment - Apply style to TabLayout for consistent appearance --- .../src/main/java/com/ichi2/anki/CardTemplateEditor.kt | 2 +- .../com/ichi2/anki/previewer/TemplatePreviewerFragment.kt | 1 + .../src/main/res/layout/card_template_editor_item.xml | 2 +- AnkiDroid/src/main/res/layout/template_previewer.xml | 6 +++--- AnkiDroid/src/main/res/values/attrs.xml | 1 + AnkiDroid/src/main/res/values/styles.xml | 8 ++++---- AnkiDroid/src/main/res/values/theme_black.xml | 1 + AnkiDroid/src/main/res/values/theme_dark.xml | 1 + AnkiDroid/src/main/res/values/theme_light.xml | 1 + AnkiDroid/src/main/res/values/theme_plain.xml | 1 + 10 files changed, 15 insertions(+), 9 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt index 793289806ea9..7b082c50f80e 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt @@ -166,7 +166,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { slidingTabLayout = findViewById(R.id.sliding_tabs) viewPager = findViewById(R.id.card_template_editor_pager) - setNavigationBarColor(R.attr.appBarColor) + setNavigationBarColor(R.attr.alternativeBackgroundColor) // Disable the home icon enableToolbar() diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt index c0c15e17b3df..d47bf0f06d0c 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt @@ -125,6 +125,7 @@ class TemplatePreviewerFragment : if (!navBarNeedsScrim) { window.navigationBarColor = ThemeUtils.getThemeAttrColor(this, R.attr.alternativeBackgroundColor) } + window.statusBarColor = ThemeUtils.getThemeAttrColor(this, R.attr.appBarColor) } if (inFragmentedActivity) { toolbar.setOnMenuItemClickListener(this) diff --git a/AnkiDroid/src/main/res/layout/card_template_editor_item.xml b/AnkiDroid/src/main/res/layout/card_template_editor_item.xml index bfd6d4011cd1..1efdbb00eeea 100644 --- a/AnkiDroid/src/main/res/layout/card_template_editor_item.xml +++ b/AnkiDroid/src/main/res/layout/card_template_editor_item.xml @@ -47,7 +47,7 @@ android:layout_height="wrap_content" android:layout_gravity="bottom" style="@style/BottomNavigationViewStyle" - android:background="?attr/appBarColor" + android:background="?attr/alternativeBackgroundColor" app:menu="@menu/card_template_editor_navigation_bar_menu" /> diff --git a/AnkiDroid/src/main/res/layout/template_previewer.xml b/AnkiDroid/src/main/res/layout/template_previewer.xml index 6bc88681d819..93b63c972bbb 100644 --- a/AnkiDroid/src/main/res/layout/template_previewer.xml +++ b/AnkiDroid/src/main/res/layout/template_previewer.xml @@ -22,7 +22,7 @@ + diff --git a/AnkiDroid/src/main/res/values/styles.xml b/AnkiDroid/src/main/res/values/styles.xml index d7125d67482e..48d925bbac0c 100644 --- a/AnkiDroid/src/main/res/values/styles.xml +++ b/AnkiDroid/src/main/res/values/styles.xml @@ -140,10 +140,10 @@ @style/ThemeOverlay.App.BottomNavigationView diff --git a/AnkiDroid/src/main/res/values/theme_black.xml b/AnkiDroid/src/main/res/values/theme_black.xml index 9998e386bb56..197ffb1b154f 100644 --- a/AnkiDroid/src/main/res/values/theme_black.xml +++ b/AnkiDroid/src/main/res/values/theme_black.xml @@ -26,6 +26,7 @@ @color/theme_black_primary_dark @color/theme_black_primary_dark + @color/material_blue_500 @drawable/item_background_selected @color/theme_black_row_current diff --git a/AnkiDroid/src/main/res/values/theme_dark.xml b/AnkiDroid/src/main/res/values/theme_dark.xml index 106e7841acfb..cdf09182657b 100644 --- a/AnkiDroid/src/main/res/values/theme_dark.xml +++ b/AnkiDroid/src/main/res/values/theme_dark.xml @@ -49,6 +49,7 @@ @color/theme_dark_primary @color/theme_dark_primary + @color/material_blue_500 @drawable/item_background_selected @color/theme_dark_row_current diff --git a/AnkiDroid/src/main/res/values/theme_light.xml b/AnkiDroid/src/main/res/values/theme_light.xml index fe28de114ee3..d60bdc5b2df9 100644 --- a/AnkiDroid/src/main/res/values/theme_light.xml +++ b/AnkiDroid/src/main/res/values/theme_light.xml @@ -41,6 +41,7 @@ ?attr/colorPrimary ?attr/colorPrimary + @color/white @drawable/item_background_selected #ececec diff --git a/AnkiDroid/src/main/res/values/theme_plain.xml b/AnkiDroid/src/main/res/values/theme_plain.xml index af07be39e454..6ed849063c7e 100644 --- a/AnkiDroid/src/main/res/values/theme_plain.xml +++ b/AnkiDroid/src/main/res/values/theme_plain.xml @@ -22,6 +22,7 @@ @color/theme_plain_primary @color/theme_plain_primary + @color/white @color/theme_plain_primary_light From 35cfd4de332fd575a7df38b7b97f44e8f90b831d Mon Sep 17 00:00:00 2001 From: SanjaySargam Date: Mon, 10 Jun 2024 23:54:14 +0530 Subject: [PATCH 9/9] load fragment after one second of text change refresh fragment after one second of text changed. And hide preview icon if fragmented as there is no point of having this option because the fragment will auto-refresh the content. --- .../java/com/ichi2/anki/CardTemplateEditor.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt index 7b082c50f80e..1d09467bbbb9 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt @@ -21,6 +21,8 @@ import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.text.Editable import android.text.TextWatcher import android.view.ActionMode @@ -65,6 +67,7 @@ import com.ichi2.anki.previewer.TemplatePreviewerArguments import com.ichi2.anki.previewer.TemplatePreviewerFragment import com.ichi2.anki.snackbar.showSnackbar import com.ichi2.anki.utils.ext.isImageOcclusion +import com.ichi2.anki.utils.postDelayed import com.ichi2.annotations.NeedsTest import com.ichi2.compat.CompatHelper.Companion.getSerializableCompat import com.ichi2.libanki.Collection @@ -87,6 +90,7 @@ import timber.log.Timber import java.util.regex.Pattern import kotlin.math.max import kotlin.math.min +import kotlin.time.Duration.Companion.seconds /** * Allows the user to view the template for the current note type @@ -414,6 +418,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { } class CardTemplateFragment : Fragment() { + private val refreshFragmentHandler = Handler(Looper.getMainLooper()) private var currentEditorTitle: FixedTextView? = null private lateinit var editorEditText: FixedEditText @@ -463,7 +468,14 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { // Set text change listeners val templateEditorWatcher: TextWatcher = object : TextWatcher { + /** + * Declare a nullable variable refreshFragmentRunnable of type Runnable. + * This will hold a reference to the Runnable that refreshes the previewer fragment. + * It is used to manage delayed fragment updates and can be null if no updates in card. + */ + private var refreshFragmentRunnable: Runnable? = null override fun afterTextChanged(arg0: Editable) { + refreshFragmentRunnable?.let { refreshFragmentHandler.removeCallbacks(it) } templateEditor.tabToCursorPosition[cardIndex] = editorEditText.selectionStart when (currentEditorViewId) { R.id.styling_edit -> tempModel.updateCss(editorEditText.text.toString()) @@ -471,6 +483,11 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { else -> template.put("qfmt", editorEditText.text) } templateEditor.tempModel!!.updateTemplate(cardIndex, template) + val updateRunnable = Runnable { + templateEditor.loadTemplatePreviewerFragmentIfFragmented() + } + refreshFragmentRunnable = updateRunnable + refreshFragmentHandler.postDelayed(updateRunnable, REFRESH_PREVIEW_DELAY) } override fun beforeTextChanged(arg0: CharSequence, arg1: Int, arg2: Int, arg3: Int) { @@ -755,6 +772,11 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { menu.findItem(R.id.action_delete).isVisible = false } + // Hide preview option if the view is big enough + if (templateEditor.fragmented) { + menu.findItem(R.id.action_preview).isVisible = false + } + // marked insert field menu item invisible for style view val isInsertFieldItemVisible = currentEditorViewId != R.id.styling_edit menu.findItem(R.id.action_insert_field).isVisible = isInsertFieldItemVisible @@ -1198,6 +1220,9 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { private const val EDITOR_START_ORD_ID = "ordId" private const val CARD_INDEX = "card_ord" + // Time to wait before refreshing the previewer + private val REFRESH_PREVIEW_DELAY = 1.seconds + @Suppress("unused") private const val REQUEST_PREVIEWER = 0