From 77f2ec26e096530032e26aa1429861828c2a7402 Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Wed, 29 Sep 2021 20:20:14 -0500 Subject: [PATCH] Refactor how the OPDS catalog UI is built --- .../catalogs/CatalogFeedListAdapter.kt | 12 +- .../r2/testapp/catalogs/CatalogFragment.kt | 146 ++++-------------- .../r2/testapp/catalogs/GroupAdapter.kt | 100 ++++++++++++ .../r2/testapp/catalogs/NavigationAdapter.kt | 76 +++++++++ ...ogListAdapter.kt => PublicationAdapter.kt} | 10 +- ...agment.kt => PublicationDetailFragment.kt} | 8 +- .../src/main/res/layout/fragment_catalog.xml | 20 ++- .../res/layout/fragment_catalog_feed_list.xml | 4 +- ...il.xml => fragment_publication_detail.xml} | 2 +- .../src/main/res/layout/item_group_view.xml | 22 +++ .../main/res/layout/item_recycle_button.xml | 23 +++ .../res/layout/item_recycle_catalog_list.xml | 32 ---- .../res/layout/item_recycle_horizontal.xml | 45 ++++++ .../src/main/res/navigation/navigation.xml | 8 +- 14 files changed, 332 insertions(+), 176 deletions(-) create mode 100644 test-app/src/main/java/org/readium/r2/testapp/catalogs/GroupAdapter.kt create mode 100644 test-app/src/main/java/org/readium/r2/testapp/catalogs/NavigationAdapter.kt rename test-app/src/main/java/org/readium/r2/testapp/catalogs/{CatalogListAdapter.kt => PublicationAdapter.kt} (90%) rename test-app/src/main/java/org/readium/r2/testapp/catalogs/{CatalogDetailFragment.kt => PublicationDetailFragment.kt} (90%) rename test-app/src/main/res/layout/{fragment_catalog_detail.xml => fragment_publication_detail.xml} (97%) create mode 100644 test-app/src/main/res/layout/item_group_view.xml create mode 100644 test-app/src/main/res/layout/item_recycle_button.xml delete mode 100644 test-app/src/main/res/layout/item_recycle_catalog_list.xml create mode 100644 test-app/src/main/res/layout/item_recycle_horizontal.xml diff --git a/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogFeedListAdapter.kt b/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogFeedListAdapter.kt index a714d1f067..747f1df985 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogFeedListAdapter.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogFeedListAdapter.kt @@ -9,13 +9,12 @@ package org.readium.r2.testapp.catalogs import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.os.bundleOf -import androidx.databinding.DataBindingUtil import androidx.navigation.Navigation import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.readium.r2.testapp.R -import org.readium.r2.testapp.databinding.ItemRecycleCatalogListBinding +import org.readium.r2.testapp.databinding.ItemRecycleButtonBinding import org.readium.r2.testapp.domain.model.Catalog class CatalogFeedListAdapter(private val onLongClick: (Catalog) -> Unit) : @@ -26,10 +25,7 @@ class CatalogFeedListAdapter(private val onLongClick: (Catalog) -> Unit) : viewType: Int ): ViewHolder { return ViewHolder( - DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - R.layout.item_recycle_catalog_list, parent, false - ) + ItemRecycleButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) ) } @@ -40,11 +36,11 @@ class CatalogFeedListAdapter(private val onLongClick: (Catalog) -> Unit) : viewHolder.bind(catalog) } - inner class ViewHolder(private val binding: ItemRecycleCatalogListBinding) : + inner class ViewHolder(private val binding: ItemRecycleButtonBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(catalog: Catalog) { - binding.catalog = catalog + binding.catalogListButton.text = catalog.title binding.catalogListButton.setOnClickListener { val bundle = bundleOf(CATALOGFEED to catalog) Navigation.findNavController(it) diff --git a/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogFragment.kt b/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogFragment.kt index 6d82afaf61..af06de5546 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogFragment.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogFragment.kt @@ -7,19 +7,15 @@ package org.readium.r2.testapp.catalogs import android.os.Bundle -import android.view.* -import android.widget.Button -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView -import androidx.appcompat.content.res.AppCompatResources +import android.view.LayoutInflater +import android.view.Menu +import android.view.View +import android.view.ViewGroup import androidx.core.os.bundleOf -import androidx.core.view.setPadding import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.Navigation import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar import org.readium.r2.shared.opds.Facet import org.readium.r2.testapp.MainActivity @@ -34,7 +30,9 @@ import org.readium.r2.testapp.opds.GridAutoFitLayoutManager class CatalogFragment : Fragment() { private val catalogViewModel: CatalogViewModel by viewModels() - private lateinit var catalogListAdapter: CatalogListAdapter + private lateinit var publicationAdapter: PublicationAdapter + private lateinit var groupAdapter: GroupAdapter + private lateinit var navigationAdapter: NavigationAdapter private lateinit var catalog: Catalog private var showFacetMenu = false private lateinit var facets: MutableList @@ -42,7 +40,6 @@ class CatalogFragment : Fragment() { private var _binding: FragmentCatalogBinding? = null private val binding get() = _binding!! - // FIXME the entire way this fragment is built feels like a hack. Need a cleaner UI override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -56,12 +53,24 @@ class CatalogFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - catalogListAdapter = CatalogListAdapter() + publicationAdapter = PublicationAdapter() + navigationAdapter = NavigationAdapter(catalog.type) + groupAdapter = GroupAdapter(catalog.type) setHasOptionsMenu(true) - binding.catalogDetailList.apply { + binding.catalogNavigationList.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = navigationAdapter + addItemDecoration( + CatalogFeedListFragment.VerticalSpaceItemDecoration( + 10 + ) + ) + } + + binding.catalogPublicationsList.apply { layoutManager = GridAutoFitLayoutManager(requireContext(), 120) - adapter = catalogListAdapter + adapter = publicationAdapter addItemDecoration( BookshelfFragment.VerticalSpaceItemDecoration( 10 @@ -69,6 +78,11 @@ class CatalogFragment : Fragment() { ) } + binding.catalogGroupList.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = groupAdapter + } + (activity as MainActivity).supportActionBar?.title = catalog.title // TODO this feels hacky, I don't want to parse the file if it has not changed @@ -85,110 +99,10 @@ class CatalogFragment : Fragment() { } requireActivity().invalidateOptionsMenu() - result.feed!!.navigation.forEachIndexed { index, navigation -> - val button = Button(requireContext()) - button.apply { - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ) - text = navigation.title - setOnClickListener { - val catalog1 = Catalog( - href = navigation.href, - title = navigation.title!!, - type = catalog.type - ) - val bundle = bundleOf(CATALOGFEED to catalog1) - Navigation.findNavController(it) - .navigate(R.id.action_navigation_catalog_self, bundle) - } - } - binding.catalogLinearLayout.addView(button, index) - } - - if (result.feed!!.publications.isNotEmpty()) { - catalogListAdapter.submitList(result.feed!!.publications) - } + navigationAdapter.submitList(result.feed!!.navigation) + publicationAdapter.submitList(result.feed!!.publications) + groupAdapter.submitList(result.feed!!.groups) - for (group in result.feed!!.groups) { - if (group.publications.isNotEmpty()) { - val linearLayout = LinearLayout(requireContext()).apply { - orientation = LinearLayout.HORIZONTAL - setPadding(10) - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT, - 1f - ) - weightSum = 2f - addView(TextView(requireContext()).apply { - text = group.title - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT, - 1f - ) - }) - if (group.links.size > 0) { - addView(ImageView(requireContext()).apply { - // FIXME Have the arrow at the very end - setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_baseline_arrow_forward_24)) - contentDescription = getString(R.string.catalog_list_more) - gravity = Gravity.END - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT, - 1f - ) - setOnClickListener { - val catalog1 = Catalog( - href = group.links.first().href, - title = group.title, - type = catalog.type - ) - val bundle = bundleOf(CATALOGFEED to catalog1) - Navigation.findNavController(it) - .navigate(R.id.action_navigation_catalog_self, bundle) - } - }) - } - } - val publicationRecyclerView = RecyclerView(requireContext()).apply { - layoutManager = LinearLayoutManager(requireContext()) - (layoutManager as LinearLayoutManager).orientation = - LinearLayoutManager.HORIZONTAL - adapter = CatalogListAdapter().apply { - submitList(group.publications) - } - } - binding.catalogLinearLayout.addView(linearLayout) - binding.catalogLinearLayout.addView(publicationRecyclerView) - } - if (group.navigation.isNotEmpty()) { - for (navigation in group.navigation) { - val button = Button(requireContext()) - button.apply { - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ) - text = navigation.title - setOnClickListener { - val catalog1 = Catalog( - href = navigation.href, - title = navigation.title!!, - type = catalog.type - ) - val bundle = bundleOf(CATALOGFEED to catalog1) - Navigation.findNavController(it) - .navigate(R.id.action_navigation_catalog_self, bundle) - } - } - binding.catalogLinearLayout.addView(button) - } - } - } binding.catalogProgressBar.visibility = View.GONE }) } diff --git a/test-app/src/main/java/org/readium/r2/testapp/catalogs/GroupAdapter.kt b/test-app/src/main/java/org/readium/r2/testapp/catalogs/GroupAdapter.kt new file mode 100644 index 0000000000..b09abc20ab --- /dev/null +++ b/test-app/src/main/java/org/readium/r2/testapp/catalogs/GroupAdapter.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2021 Readium Foundation. All rights reserved. + * Use of this source code is governed by the BSD-style license + * available in the top-level LICENSE file of the project. + */ + +package org.readium.r2.testapp.catalogs + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.os.bundleOf +import androidx.navigation.Navigation +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.readium.r2.shared.opds.Group +import org.readium.r2.testapp.R +import org.readium.r2.testapp.databinding.ItemGroupViewBinding +import org.readium.r2.testapp.domain.model.Catalog + +class GroupAdapter(val type: Int) : + ListAdapter(GroupDiff()) { + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): ViewHolder { + return ViewHolder( + ItemGroupViewBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + ) + } + + override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { + val group = getItem(position) + + viewHolder.bind(group) + } + + inner class ViewHolder(private val binding: ItemGroupViewBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(group: Group) { + binding.groupViewGroupPublications.itemRecycleHeaderTitle.text = group.title + if (group.links.size > 0) { + binding.groupViewGroupPublications.itemRecycleMoreButton.visibility = View.VISIBLE + binding.groupViewGroupPublications.itemRecycleMoreButton.setOnClickListener { + val catalog1 = Catalog( + href = group.links.first().href, + title = group.title, + type = type + ) + val bundle = bundleOf(CatalogFeedListAdapter.CATALOGFEED to catalog1) + Navigation.findNavController(it) + .navigate(R.id.action_navigation_catalog_self, bundle) + } + } + binding.groupViewGroupPublications.recyclerView.apply { + layoutManager = LinearLayoutManager(binding.root.context) + (layoutManager as LinearLayoutManager).orientation = + LinearLayoutManager.HORIZONTAL + adapter = PublicationAdapter().apply { + submitList(group.publications) + } + } + binding.groupViewGroupLinks.apply { + layoutManager = LinearLayoutManager(binding.root.context) + adapter = NavigationAdapter(type).apply { + submitList(group.navigation) + } + addItemDecoration( + CatalogFeedListFragment.VerticalSpaceItemDecoration( + 10 + ) + ) + } + } + } + + private class GroupDiff : DiffUtil.ItemCallback() { + + override fun areItemsTheSame( + oldItem: Group, + newItem: Group + ): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame( + oldItem: Group, + newItem: Group + ): Boolean { + return oldItem == newItem + } + } + +} \ No newline at end of file diff --git a/test-app/src/main/java/org/readium/r2/testapp/catalogs/NavigationAdapter.kt b/test-app/src/main/java/org/readium/r2/testapp/catalogs/NavigationAdapter.kt new file mode 100644 index 0000000000..fc4244cf5d --- /dev/null +++ b/test-app/src/main/java/org/readium/r2/testapp/catalogs/NavigationAdapter.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2021 Readium Foundation. All rights reserved. + * Use of this source code is governed by the BSD-style license + * available in the top-level LICENSE file of the project. + */ + +package org.readium.r2.testapp.catalogs + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.os.bundleOf +import androidx.navigation.Navigation +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.readium.r2.shared.publication.Link +import org.readium.r2.testapp.R +import org.readium.r2.testapp.databinding.ItemRecycleButtonBinding +import org.readium.r2.testapp.domain.model.Catalog + +class NavigationAdapter(val type: Int) : + ListAdapter(LinkDiff()) { + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): ViewHolder { + return ViewHolder( + ItemRecycleButtonBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + ) + } + + override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { + val link = getItem(position) + + viewHolder.bind(link) + } + + inner class ViewHolder(private val binding: ItemRecycleButtonBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(link: Link) { + binding.catalogListButton.text = link.title + binding.catalogListButton.setOnClickListener { + val catalog1 = Catalog( + href = link.href, + title = link.title!!, + type = type + ) + val bundle = bundleOf(CatalogFeedListAdapter.CATALOGFEED to catalog1) + Navigation.findNavController(it) + .navigate(R.id.action_navigation_catalog_self, bundle) + } + } + } + + private class LinkDiff : DiffUtil.ItemCallback() { + + override fun areItemsTheSame( + oldItem: Link, + newItem: Link + ): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame( + oldItem: Link, + newItem: Link + ): Boolean { + return oldItem == newItem + } + } + +} \ No newline at end of file diff --git a/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogListAdapter.kt b/test-app/src/main/java/org/readium/r2/testapp/catalogs/PublicationAdapter.kt similarity index 90% rename from test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogListAdapter.kt rename to test-app/src/main/java/org/readium/r2/testapp/catalogs/PublicationAdapter.kt index 47c1a0c229..8c57b3cf4b 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogListAdapter.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/catalogs/PublicationAdapter.kt @@ -9,7 +9,6 @@ package org.readium.r2.testapp.catalogs import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup -import androidx.databinding.DataBindingUtil import androidx.navigation.Navigation import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter @@ -21,18 +20,15 @@ import org.readium.r2.shared.publication.opds.images import org.readium.r2.testapp.R import org.readium.r2.testapp.databinding.ItemRecycleCatalogBinding -class CatalogListAdapter : - ListAdapter(PublicationListDiff()) { +class PublicationAdapter : + ListAdapter(PublicationListDiff()) { override fun onCreateViewHolder( parent: ViewGroup, viewType: Int ): ViewHolder { return ViewHolder( - DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - R.layout.item_recycle_catalog, parent, false - ) + ItemRecycleCatalogBinding.inflate(LayoutInflater.from(parent.context), parent, false) ) } diff --git a/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogDetailFragment.kt b/test-app/src/main/java/org/readium/r2/testapp/catalogs/PublicationDetailFragment.kt similarity index 90% rename from test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogDetailFragment.kt rename to test-app/src/main/java/org/readium/r2/testapp/catalogs/PublicationDetailFragment.kt index 2ac55c9b65..388ea6cb0d 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogDetailFragment.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/catalogs/PublicationDetailFragment.kt @@ -20,15 +20,15 @@ import org.readium.r2.shared.publication.Publication import org.readium.r2.shared.publication.opds.images import org.readium.r2.testapp.MainActivity import org.readium.r2.testapp.R -import org.readium.r2.testapp.databinding.FragmentCatalogDetailBinding +import org.readium.r2.testapp.databinding.FragmentPublicationDetailBinding -class CatalogDetailFragment : Fragment() { +class PublicationDetailFragment : Fragment() { private var publication: Publication? = null private val catalogViewModel: CatalogViewModel by viewModels() - private var _binding: FragmentCatalogDetailBinding? = null + private var _binding: FragmentPublicationDetailBinding? = null private val binding get() = _binding!! override fun onCreateView( @@ -37,7 +37,7 @@ class CatalogDetailFragment : Fragment() { ): View { _binding = DataBindingUtil.inflate( LayoutInflater.from(context), - R.layout.fragment_catalog_detail, container, false + R.layout.fragment_publication_detail, container, false ) catalogViewModel.detailChannel.receive(this) { handleEvent(it) } publication = arguments?.getPublicationOrNull() diff --git a/test-app/src/main/res/layout/fragment_catalog.xml b/test-app/src/main/res/layout/fragment_catalog.xml index a7deb3a4b8..96886babd3 100644 --- a/test-app/src/main/res/layout/fragment_catalog.xml +++ b/test-app/src/main/res/layout/fragment_catalog.xml @@ -25,13 +25,31 @@ android:orientation="vertical"> + + + + diff --git a/test-app/src/main/res/layout/fragment_catalog_feed_list.xml b/test-app/src/main/res/layout/fragment_catalog_feed_list.xml index 9b2ae885d9..5aa0a78e29 100644 --- a/test-app/src/main/res/layout/fragment_catalog_feed_list.xml +++ b/test-app/src/main/res/layout/fragment_catalog_feed_list.xml @@ -1,6 +1,5 @@ + tools:listitem="@layout/item_recycle_button" /> + tools:context=".catalogs.PublicationDetailFragment"> + + + + + + + \ No newline at end of file diff --git a/test-app/src/main/res/layout/item_recycle_button.xml b/test-app/src/main/res/layout/item_recycle_button.xml new file mode 100644 index 0000000000..313d9ad24c --- /dev/null +++ b/test-app/src/main/res/layout/item_recycle_button.xml @@ -0,0 +1,23 @@ + + + + + +