From 1dd2fd3b5c867fd02963e57686d75b36592f4b76 Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Wed, 26 Jul 2023 19:41:13 -0500 Subject: [PATCH 01/12] Use accompanist for theme adapter instead of compose --- gradle/libs.versions.toml | 8 +++++--- test-app/build.gradle.kts | 4 +++- .../java/org/readium/r2/testapp/utils/compose/AppTheme.kt | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b83ae79e70..fffa05cd1e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,7 @@ [versions] +accompanist = "0.30.1" + androidx-activity = "1.7.2" androidx-appcompat = "1.6.1" androidx-browser = "1.5.0" @@ -10,7 +12,6 @@ androidx-compose-foundation = "1.4.3" androidx-compose-material = "1.4.3" androidx-compose-material3 = "1.1.1" androidx-compose-runtime = "1.4.3" -androidx-compose-theme-adapter = "1.1.19" androidx-compose-ui = "1.4.3" androidx-constraintlayout = "2.1.4" androidx-core = "1.10.1" @@ -57,6 +58,8 @@ robolectric = "4.10.3" timber = "5.0.1" [libraries] +accompanist-themeadapter-material = { group = "com.google.accompanist", name = "accompanist-themeadapter-material", version.ref = "accompanist" } + androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "androidx-activity" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" } androidx-browser = { group = "androidx.browser", name = "browser", version.ref = "androidx-browser" } @@ -67,7 +70,6 @@ androidx-compose-foundation = { group = "androidx.compose.foundation", name = "f androidx-compose-material = { group = "androidx.compose.material", name = "material", version.ref = "androidx-compose-material" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "androidx-compose-material3" } androidx-compose-material-icons = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "androidx-compose-material" } -androidx-compose-theme-adapter = { group ="com.google.android.material", name = "compose-theme-adapter", version.ref = "androidx-compose-theme-adapter" } androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "androidx-compose-ui" } androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "androidx-compose-ui" } androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "androidx-constraintlayout" } @@ -137,7 +139,7 @@ timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "tim [bundles] -compose = ["androidx-compose-activity", "androidx-compose-animation", "androidx-compose-foundation", "androidx-compose-material", "androidx-compose-material3", "androidx-compose-material-icons", "androidx-compose-theme-adapter", "androidx-compose-ui", "androidx-compose-ui-tooling"] +compose = ["androidx-compose-activity", "androidx-compose-animation", "androidx-compose-foundation", "androidx-compose-material", "androidx-compose-material3", "androidx-compose-material-icons", "androidx-compose-ui", "androidx-compose-ui-tooling"] coroutines = ["kotlinx-coroutines-core", "kotlinx-coroutines-android"] exoplayer = ["google-exoplayer-core", "google-exoplayer-ui", "google-exoplayer-mediasession", "google-exoplayer-workmanager", "google-exoplayer-extension-media2"] lifecycle = ["androidx-lifecycle-common", "androidx-lifecycle-extensions", "androidx-lifecycle-livedata", "androidx-lifecycle-runtime", "androidx-lifecycle-viewmodel", "androidx-lifecycle-vmsavedstate", "androidx-lifecycle-viewmodel-compose"] diff --git a/test-app/build.gradle.kts b/test-app/build.gradle.kts index 1c785ad281..49619c39ff 100644 --- a/test-app/build.gradle.kts +++ b/test-app/build.gradle.kts @@ -49,7 +49,7 @@ android { proguardFiles(getDefaultProguardFile("proguard-android.txt")) } } - packagingOptions { + packaging { resources.excludes.add("META-INF/*") } @@ -76,6 +76,8 @@ dependencies { // Only required if you want to support PDF files using PDFium. implementation(project(":readium:adapters:pdfium")) + implementation(libs.accompanist.themeadapter.material) + implementation(libs.androidx.compose.activity) implementation(libs.androidx.activity.ktx) implementation(libs.androidx.appcompat) diff --git a/test-app/src/main/java/org/readium/r2/testapp/utils/compose/AppTheme.kt b/test-app/src/main/java/org/readium/r2/testapp/utils/compose/AppTheme.kt index b26ab96095..4bae27d3de 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/utils/compose/AppTheme.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/utils/compose/AppTheme.kt @@ -7,7 +7,7 @@ package org.readium.r2.testapp.utils.compose import androidx.compose.runtime.Composable -import com.google.android.material.composethemeadapter.MdcTheme +import com.google.accompanist.themeadapter.material.MdcTheme /** * Setup the Compose app-wide theme. From f5ca52d51489d06b30b523695b86b3cab3c7f772 Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Wed, 26 Jul 2023 19:41:38 -0500 Subject: [PATCH 02/12] A few more dependency updates --- gradle/libs.versions.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fffa05cd1e..18572d6dae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,9 +25,9 @@ androidx-lifecycle-extensions = "2.2.0" androidx-media = "1.6.0" androidx-media2 = "1.2.1" androidx-media3 = "1.1.0" -androidx-navigation = "2.5.2" +androidx-navigation = "2.6.0" androidx-paging = "3.1.1" -androidx-recyclerview = "1.3.0" +androidx-recyclerview = "1.3.1" androidx-room = "2.5.2" androidx-viewpager2 = "1.0.0" androidx-webkit = "1.7.0" @@ -44,8 +44,8 @@ jsoup = "1.16.1" junit = "4.13.2" kotlin = "1.9.0" -kotlinx-coroutines = "1.7.2" -kotlinx-coroutines-test = "1.7.2" +kotlinx-coroutines = "1.7.3" +kotlinx-coroutines-test = "1.7.3" kotlinx-serialization-json = "1.5.1" pdfium = "1.8.2" From 9c3d51fd04c212c6cdb692baeebca475361f4b94 Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Wed, 26 Jul 2023 19:42:24 -0500 Subject: [PATCH 03/12] Remove unused parameter --- .../adapters/pspdfkit/navigator/PsPdfKitDocumentFragment.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitDocumentFragment.kt b/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitDocumentFragment.kt index 05999ce0c9..2c59bfb82e 100644 --- a/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitDocumentFragment.kt +++ b/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitDocumentFragment.kt @@ -55,7 +55,7 @@ internal class PsPdfKitDocumentFragment( if (field == value) return field = value - reloadDocumentAtPage(pageIndex) + reloadDocumentAtPage() } private lateinit var pdfFragment: PdfFragment @@ -73,10 +73,10 @@ internal class PsPdfKitDocumentFragment( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - reloadDocumentAtPage(initialPageIndex) + reloadDocumentAtPage() } - private fun reloadDocumentAtPage(pageIndex: Int) { + private fun reloadDocumentAtPage() { pdfFragment = createPdfFragment() childFragmentManager.commitNow { From 93c790afacc92fd86fb0ad23794037246f17adef Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Wed, 26 Jul 2023 19:46:51 -0500 Subject: [PATCH 04/12] Cleanup remaining uses of launchWhenResumed/Started --- .../navigator/epub/EpubNavigatorFragment.kt | 4 +- .../r2/navigator/pager/R2CbzPageFragment.kt | 38 ++++---- .../r2/navigator/pager/R2EpubPageFragment.kt | 90 +++++++++++-------- .../r2/testapp/reader/VisualReaderFragment.kt | 2 +- 4 files changed, 76 insertions(+), 58 deletions(-) diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt index dd2c9bea76..904acabbee 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt @@ -28,7 +28,7 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import androidx.lifecycle.whenStarted +import androidx.lifecycle.withStarted import androidx.viewpager.widget.ViewPager import kotlin.math.ceil import kotlin.reflect.KClass @@ -479,7 +479,7 @@ public class EpubNavigatorFragment internal constructor( } viewLifecycleOwner.lifecycleScope.launch { - whenStarted { + withStarted { // Restore the last locator before a configuration change (e.g. screen rotation), or the // initial locator when given. val locator = savedInstanceState?.getParcelable("locator") ?: initialLocator diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/pager/R2CbzPageFragment.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/pager/R2CbzPageFragment.kt index 0665502f03..079fdb782a 100755 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/pager/R2CbzPageFragment.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/pager/R2CbzPageFragment.kt @@ -16,7 +16,9 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import androidx.core.view.ViewCompat +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.github.chrisbanes.photoview.PhotoView import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope @@ -85,25 +87,27 @@ internal class R2CbzPageFragment( } private fun updatePadding() { - viewLifecycleOwner.lifecycleScope.launchWhenResumed { - val window = activity?.window ?: return@launchWhenResumed - var top = 0 - var bottom = 0 - - // Add additional padding to take into account the display cutout, if needed. - if ( - android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P && - window.attributes.layoutInDisplayCutoutMode != WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER - ) { - // Request the display cutout insets from the decor view because the ones given by - // setOnApplyWindowInsetsListener are not always correct for preloaded views. - window.decorView.rootWindowInsets?.displayCutout?.let { displayCutoutInsets -> - top += displayCutoutInsets.safeInsetTop - bottom += displayCutoutInsets.safeInsetBottom + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + val window = activity?.window ?: return@repeatOnLifecycle + var top = 0 + var bottom = 0 + + // Add additional padding to take into account the display cutout, if needed. + if ( + android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P && + window.attributes.layoutInDisplayCutoutMode != WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER + ) { + // Request the display cutout insets from the decor view because the ones given by + // setOnApplyWindowInsetsListener are not always correct for preloaded views. + window.decorView.rootWindowInsets?.displayCutout?.let { displayCutoutInsets -> + top += displayCutoutInsets.safeInsetTop + bottom += displayCutoutInsets.safeInsetBottom + } } - } - photoView.setPadding(0, top, 0, bottom) + photoView.setPadding(0, top, 0, bottom) + } } } } diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/pager/R2EpubPageFragment.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/pager/R2EpubPageFragment.kt index 898c03cb91..c1b7a272bf 100755 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/pager/R2EpubPageFragment.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/pager/R2EpubPageFragment.kt @@ -22,8 +22,10 @@ import android.webkit.WebView import androidx.core.view.ViewCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.webkit.WebViewClientCompat import kotlin.math.roundToInt import kotlinx.coroutines.flow.* @@ -124,7 +126,7 @@ internal class R2EpubPageFragment : Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { _binding = ReadiumNavigatorViewpagerFragmentEpubBinding.inflate(inflater, container, false) containerView = binding.root preferences = activity?.getSharedPreferences("org.readium.r2.settings", Context.MODE_PRIVATE)!! @@ -294,32 +296,36 @@ internal class R2EpubPageFragment : Fragment() { private fun updatePadding() { if (view == null) return - viewLifecycleOwner.lifecycleScope.launchWhenResumed { - val window = activity?.window ?: return@launchWhenResumed - var top = 0 - var bottom = 0 + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + val window = activity?.window ?: return@repeatOnLifecycle + var top = 0 + var bottom = 0 + + // Add additional padding to take into account the display cutout, if needed. + if ( + shouldApplyInsetsPadding && + android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P && + window.attributes.layoutInDisplayCutoutMode != WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER + ) { + // Request the display cutout insets from the decor view because the ones given by + // setOnApplyWindowInsetsListener are not always correct for preloaded views. + window.decorView.rootWindowInsets?.displayCutout?.let { displayCutoutInsets -> + top += displayCutoutInsets.safeInsetTop + bottom += displayCutoutInsets.safeInsetBottom + } + } - // Add additional padding to take into account the display cutout, if needed. - if ( - shouldApplyInsetsPadding && - android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P && - window.attributes.layoutInDisplayCutoutMode != WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER - ) { - // Request the display cutout insets from the decor view because the ones given by - // setOnApplyWindowInsetsListener are not always correct for preloaded views. - window.decorView.rootWindowInsets?.displayCutout?.let { displayCutoutInsets -> - top += displayCutoutInsets.safeInsetTop - bottom += displayCutoutInsets.safeInsetBottom + if (!viewModel.isScrollEnabled.value) { + val margin = + resources.getDimension(R.dimen.readium_navigator_epub_vertical_padding) + .toInt() + top += margin + bottom += margin } - } - if (!viewModel.isScrollEnabled.value) { - val margin = resources.getDimension(R.dimen.readium_navigator_epub_vertical_padding).toInt() - top += margin - bottom += margin + containerView.setPadding(0, top, 0, bottom) } - - containerView.setPadding(0, top, 0, bottom) } } @@ -339,17 +345,23 @@ internal class R2EpubPageFragment : Fragment() { if (view == null) return - viewLifecycleOwner.lifecycleScope.launchWhenCreated { - val webView = requireNotNull(webView) - webView.visibility = View.VISIBLE - - pendingLocator - ?.let { locator -> - loadLocator(webView, requireNotNull(navigator).presentation.value.readingProgression, locator) - } - .also { pendingLocator = null } + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) { + val webView = requireNotNull(webView) + webView.visibility = View.VISIBLE + + pendingLocator + ?.let { locator -> + loadLocator( + webView, + requireNotNull(navigator).presentation.value.readingProgression, + locator + ) + } + .also { pendingLocator = null } - webView.listener?.onPageLoaded() + webView.listener?.onPageLoaded() + } } } @@ -359,11 +371,13 @@ internal class R2EpubPageFragment : Fragment() { return } - viewLifecycleOwner.lifecycleScope.launchWhenCreated { - val webView = requireNotNull(webView) - val epubNavigator = requireNotNull(navigator) - loadLocator(webView, epubNavigator.presentation.value.readingProgression, locator) - webView.listener?.onProgressionChanged() + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) { + val webView = requireNotNull(webView) + val epubNavigator = requireNotNull(navigator) + loadLocator(webView, epubNavigator.presentation.value.readingProgression, locator) + webView.listener?.onProgressionChanged() + } } } diff --git a/test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt b/test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt index 17af993680..2bd8c0fbc6 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt @@ -152,7 +152,7 @@ abstract class VisualReaderFragment : BaseReaderFragment(), VisualNavigator.List private fun setupObservers() { viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { navigator.currentLocator .onEach { model.saveProgression(it) } .launchIn(this) From a6c18568f333db697484bb1414f8716bc20a02df Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Wed, 26 Jul 2023 19:47:18 -0500 Subject: [PATCH 05/12] Replace deprecated flags, use Builder for DeviceInfo --- .../media3/tts/session/TtsSessionAdapter.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/media3/tts/session/TtsSessionAdapter.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/media3/tts/session/TtsSessionAdapter.kt index 2100d905c5..55af1f64c2 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/media3/tts/session/TtsSessionAdapter.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/media3/tts/session/TtsSessionAdapter.kt @@ -146,11 +146,11 @@ internal class TtsSessionAdapter( COMMAND_GET_AUDIO_ATTRIBUTES, COMMAND_GET_DEVICE_VOLUME, - COMMAND_SET_DEVICE_VOLUME, + COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS, COMMAND_SET_SPEED_AND_PITCH, COMMAND_GET_CURRENT_MEDIA_ITEM, - COMMAND_GET_MEDIA_ITEMS_METADATA, + COMMAND_GET_METADATA, COMMAND_GET_TEXT ).build() @@ -825,11 +825,12 @@ internal class TtsSessionAdapter( } private fun createDeviceInfo(streamVolumeManager: StreamVolumeManager): DeviceInfo { - val newDeviceInfo = DeviceInfo( - DeviceInfo.PLAYBACK_TYPE_LOCAL, - streamVolumeManager.minVolume, - streamVolumeManager.maxVolume + val newDeviceInfo = DeviceInfo.Builder( + DeviceInfo.PLAYBACK_TYPE_LOCAL ) + .setMinVolume(streamVolumeManager.minVolume) + .setMaxVolume(streamVolumeManager.maxVolume) + .build() deviceInfo = newDeviceInfo return newDeviceInfo } From 3451fb9b5b36feece4b7933c0497b1c4bba3a259 Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Wed, 26 Jul 2023 19:47:45 -0500 Subject: [PATCH 06/12] Remove use of name shadowing --- .../java/org/readium/r2/testapp/shared/views/Preferences.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test-app/src/main/java/org/readium/r2/testapp/shared/views/Preferences.kt b/test-app/src/main/java/org/readium/r2/testapp/shared/views/Preferences.kt index 38bf618988..f4ad6a7c98 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/shared/views/Preferences.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/shared/views/Preferences.kt @@ -143,14 +143,14 @@ private fun MenuItem( ) } ) { dismiss -> - for (value in values) { + for (aValue in values) { DropdownMenuItem( onClick = { dismiss() - onValueChanged(value) + onValueChanged(aValue) } ) { - Text(formatValue(value)) + Text(formatValue(aValue)) } } } From 9408caace93ecb0b0c59ed3497aa4eec4f737196 Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Wed, 26 Jul 2023 19:49:35 -0500 Subject: [PATCH 07/12] Use MenuHost/MenuProvider instead of onCreateOptionsMenu/onOptionsItemSelected --- .../r2/testapp/catalogs/CatalogFragment.kt | 79 +++++++++++-------- .../r2/testapp/reader/BaseReaderFragment.kt | 79 +++++++++++-------- .../r2/testapp/reader/EpubReaderFragment.kt | 59 ++++++++------ .../r2/testapp/reader/VisualReaderFragment.kt | 34 +++++--- 4 files changed, 146 insertions(+), 105 deletions(-) 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 86aff3c389..d2aab8b173 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 @@ -9,11 +9,16 @@ package org.readium.r2.testapp.catalogs import android.os.Bundle import android.view.LayoutInflater import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.os.bundleOf +import androidx.core.view.MenuHost +import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle import androidx.navigation.Navigation import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar @@ -55,7 +60,6 @@ class CatalogFragment : Fragment() { publicationAdapter = PublicationAdapter(catalogViewModel::publication::set) navigationAdapter = NavigationAdapter(catalog.type) groupAdapter = GroupAdapter(catalog.type, catalogViewModel::publication::set) - setHasOptionsMenu(true) binding.catalogNavigationList.apply { layoutManager = LinearLayoutManager(requireContext()) @@ -84,12 +88,9 @@ class CatalogFragment : Fragment() { (activity as MainActivity).supportActionBar?.title = catalog.title - // TODO this feels hacky, I don't want to parse the file if it has not changed - if (catalogViewModel.parseData.value == null) { - binding.catalogProgressBar.visibility = View.VISIBLE - catalogViewModel.parseCatalog(catalog) - } - catalogViewModel.parseData.observe(viewLifecycleOwner, { result -> + // FIXME opening a catalog, then opening a different catalog does not transition well + catalogViewModel.parseCatalog(catalog) + catalogViewModel.parseData.observe(viewLifecycleOwner) { result -> facets = result.feed?.facets ?: mutableListOf() @@ -103,7 +104,44 @@ class CatalogFragment : Fragment() { groupAdapter.submitList(result.feed!!.groups) binding.catalogProgressBar.visibility = View.GONE - }) + } + + val menuHost: MenuHost = requireActivity() + + menuHost.addMenuProvider( + object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menu.clear() + if (showFacetMenu) { + facets.let { + for (i in facets.indices) { + val submenu = menu.addSubMenu(facets[i].title) + for (link in facets[i].links) { + val item = submenu.add(link.title) + item.setOnMenuItemClickListener { + val catalog1 = Catalog( + title = link.title!!, + href = link.href, + type = catalog.type + ) + val bundle = bundleOf(CATALOGFEED to catalog1) + Navigation.findNavController(requireView()) + .navigate(R.id.action_navigation_catalog_self, bundle) + true + } + } + } + } + } + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + return false + } + }, + viewLifecycleOwner, + Lifecycle.State.RESUMED + ) } private fun handleEvent(event: CatalogViewModel.Event.FeedEvent) { @@ -118,29 +156,4 @@ class CatalogFragment : Fragment() { Snackbar.LENGTH_LONG ).show() } - - override fun onPrepareOptionsMenu(menu: Menu) { - menu.clear() - if (showFacetMenu) { - facets.let { - for (i in facets.indices) { - val submenu = menu.addSubMenu(facets[i].title) - for (link in facets[i].links) { - val item = submenu.add(link.title) - item.setOnMenuItemClickListener { - val catalog1 = Catalog( - title = link.title!!, - href = link.href, - type = catalog.type - ) - val bundle = bundleOf(CATALOGFEED to catalog1) - Navigation.findNavController(requireView()) - .navigate(R.id.action_navigation_catalog_self, bundle) - true - } - } - } - } - } - } } diff --git a/test-app/src/main/java/org/readium/r2/testapp/reader/BaseReaderFragment.kt b/test-app/src/main/java/org/readium/r2/testapp/reader/BaseReaderFragment.kt index 3318834dba..f433230041 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/reader/BaseReaderFragment.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/reader/BaseReaderFragment.kt @@ -10,9 +10,13 @@ import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import android.view.View import android.widget.Toast +import androidx.core.view.MenuHost +import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle import org.readium.r2.lcp.lcpLicense import org.readium.r2.navigator.Navigator import org.readium.r2.navigator.preferences.Configurable @@ -37,7 +41,6 @@ abstract class BaseReaderFragment : Fragment() { protected abstract val navigator: Navigator override fun onCreate(savedInstanceState: Bundle?) { - setHasOptionsMenu(true) super.onCreate(savedInstanceState) model.fragmentChannel.receive(this) { event -> @@ -52,44 +55,54 @@ abstract class BaseReaderFragment : Fragment() { } } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val menuHost: MenuHost = requireActivity() + + menuHost.addMenuProvider( + object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.menu_reader, menu) + + menu.findItem(R.id.settings).isVisible = + navigator is Configurable<*, *> + + menu.findItem(R.id.drm).isVisible = + model.publication.lcpLicense != null + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + when (menuItem.itemId) { + R.id.toc -> { + model.activityChannel.send(ReaderViewModel.Event.OpenOutlineRequested) + } + R.id.bookmark -> { + model.insertBookmark(navigator.currentLocator.value) + } + R.id.settings -> { + val settingsModel = checkNotNull(model.settings) + UserPreferencesBottomSheetDialogFragment(settingsModel, "User Settings") + .show(childFragmentManager, "Settings") + } + R.id.drm -> { + model.activityChannel.send(ReaderViewModel.Event.OpenDrmManagementRequested) + } + } + return true + } + }, + viewLifecycleOwner, + Lifecycle.State.RESUMED + ) + } + override fun onHiddenChanged(hidden: Boolean) { super.onHiddenChanged(hidden) setMenuVisibility(!hidden) requireActivity().invalidateOptionsMenu() } - override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) { - menuInflater.inflate(R.menu.menu_reader, menu) - - menu.findItem(R.id.settings).isVisible = - navigator is Configurable<*, *> - - menu.findItem(R.id.drm).isVisible = - model.publication.lcpLicense != null - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.toc -> { - model.activityChannel.send(ReaderViewModel.Event.OpenOutlineRequested) - } - R.id.bookmark -> { - model.insertBookmark(navigator.currentLocator.value) - } - R.id.settings -> { - val settingsModel = checkNotNull(model.settings) - UserPreferencesBottomSheetDialogFragment(settingsModel, "User Settings") - .show(childFragmentManager, "Settings") - } - R.id.drm -> { - model.activityChannel.send(ReaderViewModel.Event.OpenDrmManagementRequested) - } - else -> return super.onOptionsItemSelected(item) - } - - return true - } - open fun go(locator: Locator, animated: Boolean) { navigator.go(locator, animated) } diff --git a/test-app/src/main/java/org/readium/r2/testapp/reader/EpubReaderFragment.kt b/test-app/src/main/java/org/readium/r2/testapp/reader/EpubReaderFragment.kt index 9447f657ff..12d4b85f35 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/reader/EpubReaderFragment.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/reader/EpubReaderFragment.kt @@ -14,6 +14,8 @@ import android.view.inputmethod.InputMethodManager import android.widget.ImageView import androidx.annotation.ColorInt import androidx.appcompat.widget.SearchView +import androidx.core.view.MenuHost +import androidx.core.view.MenuProvider import androidx.fragment.app.FragmentResultListener import androidx.fragment.app.commit import androidx.fragment.app.commitNow @@ -105,8 +107,6 @@ class EpubReaderFragment : VisualReaderFragment(), EpubNavigatorFragment.Listene } ) - setHasOptionsMenu(true) - super.onCreate(savedInstanceState) } @@ -140,6 +140,37 @@ class EpubReaderFragment : VisualReaderFragment(), EpubNavigatorFragment.Listene (navigator as? DecorableNavigator)?.applyPageNumberDecorations() } } + + val menuHost: MenuHost = requireActivity() + + menuHost.addMenuProvider( + object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuSearch = menu.findItem(R.id.search).apply { + isVisible = true + menuSearchView = actionView as SearchView + } + + connectSearch() + if (!isSearchViewIconified) menuSearch.expandActionView() + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + when (menuItem.itemId) { + R.id.search -> { + return true + } + android.R.id.home -> { + menuSearch.collapseActionView() + return true + } + } + return true + } + }, + viewLifecycleOwner, + Lifecycle.State.RESUMED + ) } /** @@ -164,18 +195,6 @@ class EpubReaderFragment : VisualReaderFragment(), EpubNavigatorFragment.Listene applyDecorations(decorations, "pageNumbers") } - override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) { - super.onCreateOptionsMenu(menu, menuInflater) - - menuSearch = menu.findItem(R.id.search).apply { - isVisible = true - menuSearchView = actionView as SearchView - } - - connectSearch() - if (!isSearchViewIconified) menuSearch.expandActionView() - } - override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putBoolean(IS_SEARCH_VIEW_ICONIFIED, isSearchViewIconified) @@ -227,18 +246,6 @@ class EpubReaderFragment : VisualReaderFragment(), EpubNavigatorFragment.Listene } } - override fun onOptionsItemSelected(item: MenuItem): Boolean = - when (item.itemId) { - R.id.search -> { - super.onOptionsItemSelected(item) - } - android.R.id.home -> { - menuSearch.collapseActionView() - true - } - else -> super.onOptionsItemSelected(item) - } - private fun showSearchFragment() { childFragmentManager.commit { childFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG)?.let { remove(it) } diff --git a/test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt b/test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt index 2bd8c0fbc6..a345ccf419 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt @@ -30,6 +30,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp +import androidx.core.view.MenuHost +import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope @@ -132,6 +134,25 @@ abstract class VisualReaderFragment : BaseReaderFragment(), VisualNavigator.List content = { Overlay() } ) } + + val menuHost: MenuHost = requireActivity() + + menuHost.addMenuProvider( + object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menu.findItem(R.id.tts).isVisible = (model.tts != null) + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + when (menuItem.itemId) { + R.id.tts -> checkNotNull(model.tts).start(navigator) + } + return true + } + }, + viewLifecycleOwner, + Lifecycle.State.RESUMED + ) } @Composable @@ -271,19 +292,6 @@ abstract class VisualReaderFragment : BaseReaderFragment(), VisualNavigator.List requireActivity().invalidateOptionsMenu() } - override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) { - super.onCreateOptionsMenu(menu, menuInflater) - menu.findItem(R.id.tts).isVisible = (model.tts != null) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.tts -> checkNotNull(model.tts).start(navigator) - else -> return super.onOptionsItemSelected(item) - } - return true - } - // DecorableNavigator.Listener private val decorationListener by lazy { DecorationListener() } From 1f4c3f1af080f7bf413ee41d1dd99e8133c6f18d Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Thu, 27 Jul 2023 00:00:04 -0500 Subject: [PATCH 08/12] Better handling of showing OPDS catalogs --- .../r2/testapp/catalogs/CatalogFragment.kt | 46 +++++++++---------- .../r2/testapp/catalogs/CatalogViewModel.kt | 5 +- 2 files changed, 25 insertions(+), 26 deletions(-) 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 d2aab8b173..f8897cfa16 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 @@ -88,23 +88,8 @@ class CatalogFragment : Fragment() { (activity as MainActivity).supportActionBar?.title = catalog.title - // FIXME opening a catalog, then opening a different catalog does not transition well catalogViewModel.parseCatalog(catalog) - catalogViewModel.parseData.observe(viewLifecycleOwner) { result -> - - facets = result.feed?.facets ?: mutableListOf() - - if (facets.size > 0) { - showFacetMenu = true - } - requireActivity().invalidateOptionsMenu() - - navigationAdapter.submitList(result.feed!!.navigation) - publicationAdapter.submitList(result.feed!!.publications) - groupAdapter.submitList(result.feed!!.groups) - - binding.catalogProgressBar.visibility = View.GONE - } + binding.catalogProgressBar.visibility = View.VISIBLE val menuHost: MenuHost = requireActivity() @@ -145,15 +130,28 @@ class CatalogFragment : Fragment() { } private fun handleEvent(event: CatalogViewModel.Event.FeedEvent) { - val message = - when (event) { - is CatalogViewModel.Event.FeedEvent.CatalogParseFailed -> getString(R.string.failed_parsing_catalog) + when (event) { + is CatalogViewModel.Event.FeedEvent.CatalogParseFailed -> { + Snackbar.make( + requireView(), + getString(R.string.failed_parsing_catalog), + Snackbar.LENGTH_LONG + ).show() } + + is CatalogViewModel.Event.FeedEvent.CatalogParseSuccess -> { + facets = event.result.feed?.facets ?: mutableListOf() + + if (facets.size > 0) { + showFacetMenu = true + } + requireActivity().invalidateOptionsMenu() + + navigationAdapter.submitList(event.result.feed!!.navigation) + publicationAdapter.submitList(event.result.feed!!.publications) + groupAdapter.submitList(event.result.feed!!.groups) + } + } binding.catalogProgressBar.visibility = View.GONE - Snackbar.make( - requireView(), - message, - Snackbar.LENGTH_LONG - ).show() } } diff --git a/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogViewModel.kt b/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogViewModel.kt index 997f9584cb..95ab731def 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogViewModel.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogViewModel.kt @@ -37,7 +37,6 @@ class CatalogViewModel(application: Application) : AndroidViewModel(application) val detailChannel = EventChannel(Channel(Channel.BUFFERED), viewModelScope) val eventChannel = EventChannel(Channel(Channel.BUFFERED), viewModelScope) - val parseData = MutableLiveData() lateinit var publication: Publication fun parseCatalog(catalog: Catalog) = viewModelScope.launch { @@ -55,7 +54,7 @@ class CatalogViewModel(application: Application) : AndroidViewModel(application) } } parseRequest?.onSuccess { - parseData.postValue(it) + eventChannel.send(Event.FeedEvent.CatalogParseSuccess(it)) } parseRequest?.onFailure { Timber.e(it) @@ -96,6 +95,8 @@ class CatalogViewModel(application: Application) : AndroidViewModel(application) sealed class FeedEvent : Event() { object CatalogParseFailed : FeedEvent() + + class CatalogParseSuccess(val result: ParseData): FeedEvent() } sealed class DetailEvent : Event() { From a10aed582765802812f4e09c3355610ba0035d9d Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Thu, 27 Jul 2023 21:15:17 -0500 Subject: [PATCH 09/12] Last few updates --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 18572d6dae..af4127b85d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ androidx-activity = "1.7.2" androidx-appcompat = "1.6.1" androidx-browser = "1.5.0" androidx-cardview = "1.0.0" -androidx-compose-compiler = "1.5.0" +androidx-compose-compiler = "1.5.1" androidx-compose-animation = "1.4.3" androidx-compose-foundation = "1.4.3" androidx-compose-material = "1.4.3" @@ -18,7 +18,7 @@ androidx-core = "1.10.1" androidx-datastore = "1.0.0" androidx-expresso-core = "3.5.1" androidx-ext-junit = "1.1.5" -androidx-fragment-ktx = "1.6.0" +androidx-fragment-ktx = "1.6.1" androidx-legacy = "1.0.0" androidx-lifecycle = "2.6.1" androidx-lifecycle-extensions = "2.2.0" @@ -26,7 +26,7 @@ androidx-media = "1.6.0" androidx-media2 = "1.2.1" androidx-media3 = "1.1.0" androidx-navigation = "2.6.0" -androidx-paging = "3.1.1" +androidx-paging = "3.2.0" androidx-recyclerview = "1.3.1" androidx-room = "2.5.2" androidx-viewpager2 = "1.0.0" From 80f7108eddf528755428acd1c285122e77072b86 Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Thu, 27 Jul 2023 21:15:27 -0500 Subject: [PATCH 10/12] Add background color back in --- test-app/src/main/res/layout/section_header.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/test-app/src/main/res/layout/section_header.xml b/test-app/src/main/res/layout/section_header.xml index b2afa8ab6c..13e0974f53 100644 --- a/test-app/src/main/res/layout/section_header.xml +++ b/test-app/src/main/res/layout/section_header.xml @@ -9,6 +9,7 @@ android:id="@+id/header" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="#eceaeb" android:orientation="vertical" android:ellipsize="end" android:maxLines="3" From f05182bb75c840b2dd631e85d9ec63d1fb945289 Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Thu, 27 Jul 2023 21:15:35 -0500 Subject: [PATCH 11/12] Fix lint --- .../java/org/readium/r2/testapp/catalogs/CatalogViewModel.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogViewModel.kt b/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogViewModel.kt index 95ab731def..7aa15b19fb 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogViewModel.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/catalogs/CatalogViewModel.kt @@ -8,7 +8,6 @@ package org.readium.r2.testapp.catalogs import android.app.Application import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import java.io.File import java.net.MalformedURLException @@ -96,7 +95,7 @@ class CatalogViewModel(application: Application) : AndroidViewModel(application) object CatalogParseFailed : FeedEvent() - class CatalogParseSuccess(val result: ParseData): FeedEvent() + class CatalogParseSuccess(val result: ParseData) : FeedEvent() } sealed class DetailEvent : Event() { From 400ae5dce78ebfe276d0273351c4253374b5d9c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Fri, 28 Jul 2023 11:31:33 +0200 Subject: [PATCH 12/12] Adjust search colors --- test-app/src/main/res/layout/fragment_search.xml | 1 - test-app/src/main/res/layout/section_header.xml | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test-app/src/main/res/layout/fragment_search.xml b/test-app/src/main/res/layout/fragment_search.xml index a7f1c4c466..125aa93c46 100644 --- a/test-app/src/main/res/layout/fragment_search.xml +++ b/test-app/src/main/res/layout/fragment_search.xml @@ -17,7 +17,6 @@ android:id="@+id/search_recyclerView" android:layout_width="0dp" android:layout_height="0dp" - android:background="@android:color/white" app:layout_constraintBottom_toBottomOf="@id/search_overlay" app:layout_constraintEnd_toEndOf="@id/search_overlay" app:layout_constraintStart_toStartOf="@id/search_overlay" diff --git a/test-app/src/main/res/layout/section_header.xml b/test-app/src/main/res/layout/section_header.xml index 13e0974f53..93b05d379a 100644 --- a/test-app/src/main/res/layout/section_header.xml +++ b/test-app/src/main/res/layout/section_header.xml @@ -9,7 +9,8 @@ android:id="@+id/header" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="#eceaeb" + android:background="?attr/colorPrimarySurface" + android:textColor="?attr/colorOnPrimarySurface" android:orientation="vertical" android:ellipsize="end" android:maxLines="3"