From 974cc7f5f29633b8ba17ba0cea7408370d638394 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Mon, 27 Jul 2026 07:52:53 +0200 Subject: [PATCH] Offer the ad removal from the settings section It was the last item in the toolbar menu on the landing screen, and with the open action gone it was the only one - a screen whose entire content is a list kept an overflow menu alive for a single row that belongs in that list. It now sits under the settings header, next to the catch-all switch, and the landing screen has no menu at all. The row is only built when there is something to sell: never in pro, where the purchase is implied, and not once it has been bought. LandingFragment asks the activity at render time rather than reading it from the state, because billing is set up by initializeProprietaryLibraries - which runs again after the play services dialog - and is not something the ViewModel could read off disk itself. That takes it out of the document view too, where the overflow menu carried it. What is left there is the snackbar that offers the purchase on entering fullscreen, which is untouched. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01RxF8q9WCwGcu5AHr9yZber --- .../droid/ui/activity/LandingFragment.kt | 18 +++++++++++++++ .../droid/ui/activity/MainActivity.kt | 23 ++++++++++--------- .../droid/ui/widget/LandingItem.kt | 1 + app/src/main/res/drawable/ic_block.xml | 10 ++++++++ app/src/main/res/menu/menu_main.xml | 4 ---- 5 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 app/src/main/res/drawable/ic_block.xml diff --git a/app/src/main/java/app/opendocument/droid/ui/activity/LandingFragment.kt b/app/src/main/java/app/opendocument/droid/ui/activity/LandingFragment.kt index 03d4a9d7949b..146e3c91b54a 100644 --- a/app/src/main/java/app/opendocument/droid/ui/activity/LandingFragment.kt +++ b/app/src/main/java/app/opendocument/droid/ui/activity/LandingFragment.kt @@ -315,6 +315,19 @@ class LandingFragment : Fragment(), LandingAdapter.Listener { state.catchAllEnabled, ) ) + + // asked here rather than carried in the state: what billing knows lives on the activity, + // not in anything the ViewModel reads. it is only ever false in pro or after a purchase, + // both of which are settled long before the first refresh comes back + if (mainActivity.offersAdRemoval()) { + items.add( + LandingItem.Action( + LandingItem.ACTION_REMOVE_ADS, + R.string.menu_remove_ads, + R.drawable.ic_block, + ) + ) + } } /** "2 hours ago", or nothing at all for entries written before this was recorded. */ @@ -417,6 +430,11 @@ class LandingFragment : Fragment(), LandingAdapter.Listener { when (action) { LandingItem.ACTION_ADD_FOLDER -> addFolder() LandingItem.ACTION_UP -> viewModel.leaveFolder() + LandingItem.ACTION_REMOVE_ADS -> { + mainActivity.analyticsManager.report("settings_remove_ads") + + mainActivity.buyAdRemoval() + } } } diff --git a/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt b/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt index a5688407183c..30779b51bdfb 100644 --- a/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt +++ b/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt @@ -365,10 +365,6 @@ class MainActivity : AppCompatActivity(), MenuProvider { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { menuInflater.inflate(R.menu.menu_main, menu) - - if (billingManager.hasPurchased()) { - menu.findItem(R.id.menu_remove_ads).isVisible = false - } } // The play services availability dialog calls startActivityForResult() itself with a @@ -470,12 +466,6 @@ class MainActivity : AppCompatActivity(), MenuProvider { analyticsManager.report("menu_share") } - R.id.menu_remove_ads -> { - analyticsManager.report("menu_remove_ads") - - buyAdRemoval() - } - R.id.menu_fullscreen -> { if (fullscreen) { analyticsManager.report("menu_fullscreen_leave") @@ -572,7 +562,18 @@ class MainActivity : AppCompatActivity(), MenuProvider { ttsActionMode = null } - private fun buyAdRemoval() { + /** + * Whether the ad removal is still worth offering: never in pro, where the purchase is implied, + * and not once it has been bought. + * + * The landing screen asks rather than being told, because billing is set up by + * [initializeProprietaryLibraries] - which can run a second time, after the play services + * dialog - and it is not something the ViewModel could read off disk itself. + */ + fun offersAdRemoval(): Boolean = + ::billingManager.isInitialized && !billingManager.hasPurchased() + + fun buyAdRemoval() { analyticsManager.report(AnalyticsConstants.EVENT_ADD_TO_CART) // the play listing id is the applicationId, which stays at.tomtasche.reader.pro diff --git a/app/src/main/java/app/opendocument/droid/ui/widget/LandingItem.kt b/app/src/main/java/app/opendocument/droid/ui/widget/LandingItem.kt index e6d74cca2fb5..f94f2cc28e16 100644 --- a/app/src/main/java/app/opendocument/droid/ui/widget/LandingItem.kt +++ b/app/src/main/java/app/opendocument/droid/ui/widget/LandingItem.kt @@ -67,6 +67,7 @@ sealed class LandingItem { companion object { const val ACTION_ADD_FOLDER: Int = 1 const val ACTION_UP: Int = 2 + const val ACTION_REMOVE_ADS: Int = 3 const val SETTING_CATCH_ALL: Int = 1 } diff --git a/app/src/main/res/drawable/ic_block.xml b/app/src/main/res/drawable/ic_block.xml new file mode 100644 index 000000000000..53979f05ff4c --- /dev/null +++ b/app/src/main/res/drawable/ic_block.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index 7e8de18e11dc..5e0e7c730bfe 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -56,8 +56,4 @@ android:visible="false" app:showAsAction="never" /> -