Skip to content

Commit

Permalink
fix: template previewer UI
Browse files Browse the repository at this point in the history
the template previewer was tightly coupled with the template editor, and that
isn't a good design. To solve that, I decoupled the template previewer
in two fragments: a view and a standalone page. All of the original
style of the template previewer was fixed.

I gave credits to BrayanDSO in TemplatePreviewerPage.kt because most of
the code is his and I don't want the credits myself

I also removed the duplicated TabLayout in the card editor because it
didn't make sense to me.
  • Loading branch information
RobozinhoD committed Aug 17, 2024
1 parent fa69fc4 commit 6e5b570
Show file tree
Hide file tree
Showing 14 changed files with 268 additions and 193 deletions.
19 changes: 11 additions & 8 deletions AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.CheckResult
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.appcompat.widget.ThemeUtils
import androidx.core.view.MenuHost
import androidx.core.view.MenuProvider
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.fragment.app.commitNow
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
Expand All @@ -65,6 +66,7 @@ import com.ichi2.anki.notetype.RenameCardTemplateDialog
import com.ichi2.anki.notetype.RepositionCardTemplateDialog
import com.ichi2.anki.previewer.TemplatePreviewerArguments
import com.ichi2.anki.previewer.TemplatePreviewerFragment
import com.ichi2.anki.previewer.TemplatePreviewerPage
import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.anki.utils.ext.isImageOcclusion
import com.ichi2.anki.utils.postDelayed
Expand Down Expand Up @@ -160,7 +162,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener {
tempModel = CardTemplateNotetype.fromBundle(savedInstanceState)
}

templatePreviewerFrame = findViewById(R.id.template_previewer_fragment)
templatePreviewerFrame = findViewById(R.id.fragment_container)
/**
* 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.
Expand Down Expand Up @@ -198,13 +200,14 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener {
ord = ord,
fields = note.fields,
tags = note.tags,
fillEmpty = true,
inFragmentedActivity = fragmented
fillEmpty = true
)
val details = TemplatePreviewerFragment.newInstance(args)
supportFragmentManager.commit {
replace(R.id.template_previewer_fragment, details)
val fragment = TemplatePreviewerFragment.newInstance(args)
supportFragmentManager.commitNow {
replace(R.id.fragment_container, fragment)
}
val backgroundColor = ThemeUtils.getThemeAttrColor(this@CardTemplateEditor, R.attr.alternativeBackgroundColor)
fragment.requireView().setBackgroundColor(backgroundColor)
}
}

Expand Down Expand Up @@ -898,7 +901,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener {
tags = note.tags,
fillEmpty = true
)
val intent = TemplatePreviewerFragment.getIntent(requireContext(), args)
val intent = TemplatePreviewerPage.getIntent(requireContext(), args)
startActivity(intent)
}
}
Expand Down
4 changes: 2 additions & 2 deletions AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ import com.ichi2.anki.noteeditor.Toolbar.TextWrapper
import com.ichi2.anki.pages.ImageOcclusion
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.previewer.TemplatePreviewerArguments
import com.ichi2.anki.previewer.TemplatePreviewerFragment
import com.ichi2.anki.previewer.TemplatePreviewerPage
import com.ichi2.anki.receiver.SdCardReceiver
import com.ichi2.anki.servicelayer.LanguageHintService
import com.ichi2.anki.servicelayer.NoteService
Expand Down Expand Up @@ -1433,7 +1433,7 @@ class NoteEditor : AnkiFragment(R.layout.note_editor), DeckSelectionListener, Su
ord = ord,
fillEmpty = false
)
val intent = TemplatePreviewerFragment.getIntent(requireContext(), args)
val intent = TemplatePreviewerPage.getIntent(requireContext(), args)
startActivity(intent)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.widget.ThemeUtils
import androidx.fragment.app.Fragment
import com.ichi2.anki.R
import com.ichi2.anki.SingleFragmentActivity
import com.ichi2.anki.utils.navBarNeedsScrim
Expand All @@ -41,7 +42,7 @@ class CardViewerActivity : SingleFragmentActivity() {
}

companion object {
fun getIntent(context: Context, fragmentClass: KClass<out CardViewerFragment>, arguments: Bundle? = null): Intent {
fun getIntent(context: Context, fragmentClass: KClass<out Fragment>, arguments: Bundle? = null): Intent {
return Intent(context, CardViewerActivity::class.java).apply {
putExtra(FRAGMENT_NAME_EXTRA, fragmentClass.jvmName)
putExtra(FRAGMENT_ARGS_EXTRA, arguments)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import kotlinx.coroutines.flow.onEach
import timber.log.Timber

abstract class CardViewerFragment(@LayoutRes layout: Int) : Fragment(layout) {
protected abstract val viewModel: CardViewerViewModel
abstract val viewModel: CardViewerViewModel
protected abstract val webView: WebView

@CallSuper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,47 +15,30 @@
*/
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.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
import com.ichi2.anki.snackbar.SnackbarBuilder
import com.ichi2.anki.utils.ext.sharedPrefs
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber

class TemplatePreviewerFragment :
CardViewerFragment(R.layout.template_previewer),
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.
*/
private var inFragmentedActivity = false
private lateinit var templatePreviewerArguments: TemplatePreviewerArguments
BaseSnackbarBuilderProvider {

override val viewModel: TemplatePreviewerViewModel by viewModels {
templatePreviewerArguments = BundleCompat.getParcelable(requireArguments(), ARGS_KEY, TemplatePreviewerArguments::class.java)!!
TemplatePreviewerViewModel.factory(templatePreviewerArguments, CardMediaPlayer())
val arguments = BundleCompat.getParcelable(requireArguments(), ARGS_KEY, TemplatePreviewerArguments::class.java)!!
TemplatePreviewerViewModel.factory(arguments, CardMediaPlayer())
}
override val webView: WebView
get() = requireView().findViewById(R.id.webview)
Expand All @@ -66,19 +49,6 @@ class TemplatePreviewerFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

val toolbar = view.findViewById<Toolbar>(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<MaterialButton>(R.id.show_answer).apply {
setOnClickListener { viewModel.toggleShowAnswer() }
}
Expand All @@ -92,44 +62,9 @@ class TemplatePreviewerFragment :
}
.launchIn(lifecycleScope)

val tabLayout = view.findViewById<TabLayout>(R.id.tab_layout)
lifecycleScope.launch {
for (templateName in viewModel.getTemplateNames()) {
tabLayout.addTab(tabLayout.newTab().setText(templateName))
}
tabLayout.selectTab(tabLayout.getTabAt(viewModel.getCurrentTabIndex()))
tabLayout.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
Timber.v("Selected tab %d", tab.position)
viewModel.onTabSelected(tab.position)
}

override fun onTabUnselected(tab: TabLayout.Tab) {
// do nothing
}

override fun onTabReselected(tab: TabLayout.Tab) {
// do nothing
}
})
}

if (sharedPrefs().getBoolean("safeDisplay", false)) {
view.findViewById<MaterialCardView>(R.id.webview_container).elevation = 0F
}

with(requireActivity()) {
window.statusBarColor = ThemeUtils.getThemeAttrColor(this, R.attr.appBarColor)
}
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 {
Expand All @@ -140,13 +75,5 @@ class TemplatePreviewerFragment :
this.arguments = bundleOf(ARGS_KEY to arguments)
}
}

fun getIntent(context: Context, arguments: TemplatePreviewerArguments): Intent {
return CardViewerActivity.getIntent(
context,
TemplatePreviewerFragment::class,
bundleOf(ARGS_KEY to arguments)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2024 Brayan Oliveira <[email protected]>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.ichi2.anki.previewer

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.core.os.BundleCompat
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.commitNow
import androidx.lifecycle.lifecycleScope
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import com.ichi2.anki.R
import com.ichi2.anki.previewer.TemplatePreviewerFragment.Companion.ARGS_KEY
import kotlinx.coroutines.launch
import timber.log.Timber

/**
* Container for [TemplatePreviewerFragment] that works as a standalone page
* by including a toolbar and a TabLayout for changing the current template.
*/
class TemplatePreviewerPage : Fragment(R.layout.template_previewer_container) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
view.findViewById<MaterialToolbar>(R.id.toolbar).setNavigationOnClickListener {
requireActivity().onBackPressedDispatcher.onBackPressed()
}

val arguments = BundleCompat.getParcelable(requireArguments(), ARGS_KEY, TemplatePreviewerArguments::class.java)!!
val fragment = TemplatePreviewerFragment.newInstance(arguments)
childFragmentManager.commitNow {
replace(R.id.fragment_container, fragment)
}

val viewModel = fragment.viewModel
val tabLayout = view.findViewById<TabLayout>(R.id.tab_layout)

lifecycleScope.launch {
for (templateName in viewModel.getTemplateNames()) {
tabLayout.addTab(tabLayout.newTab().setText(templateName))
}
tabLayout.selectTab(tabLayout.getTabAt(viewModel.getCurrentTabIndex()))
tabLayout.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
Timber.v("Selected tab %d", tab.position)
viewModel.onTabSelected(tab.position)
}

override fun onTabUnselected(tab: TabLayout.Tab) {
// do nothing
}

override fun onTabReselected(tab: TabLayout.Tab) {
// do nothing
}
})
}
}

companion object {
fun getIntent(context: Context, arguments: TemplatePreviewerArguments): Intent {
return CardViewerActivity.getIntent(
context,
TemplatePreviewerPage::class,
bundleOf(ARGS_KEY to arguments)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,7 @@ data class TemplatePreviewerArguments(
val tags: MutableList<String>,
val id: Long = 0,
val ord: Int = 0,
val fillEmpty: Boolean = false,
val inFragmentedActivity: Boolean = false
val fillEmpty: Boolean = false
) : Parcelable {
val notetype: NotetypeJson get() = notetypeFile.getNotetype()
}
46 changes: 31 additions & 15 deletions AnkiDroid/src/main/res/layout-xlarge/card_template_editor.xml
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/card_template_editor_xl_view"
android:layout_height="match_parent"
android:id="@+id/root_layout"
xmlns:app="http://schemas.android.com/apk/res-auto">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:orientation="horizontal">
<include layout="@layout/card_template_editor_activity"
android:layout_width="1dip"
android:layout_weight="1"
android:layout_height="match_parent"/>
android:layout_height="match_parent">

<include layout="@layout/card_template_editor_top"
android:id="@+id/template_editor_top"
app:layout_constraintTop_toTopOf="parent"/>

<include layout="@layout/card_template_editor_main"
android:id="@+id/template_editor"
android:layout_height="0dp"
android:layout_width="0dp"
app:layout_constraintTop_toBottomOf="@id/template_editor_top"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/fragment_container"/>

<androidx.fragment.app.FragmentContainerView
android:id="@+id/template_previewer_fragment"
android:layout_weight="1"
android:layout_width="1dip"
android:layout_height="match_parent"/>
</LinearLayout>
android:id="@+id/fragment_container"
android:layout_height="0dp"
android:layout_width="0dp"
app:layout_constraintTop_toBottomOf="@id/template_editor_top"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/template_editor"
app:layout_constraintEnd_toEndOf="parent"
/>

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Loading

0 comments on commit 6e5b570

Please sign in to comment.