diff --git a/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt b/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt index 6c8647c62df6..32b8a2c71471 100644 --- a/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt +++ b/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt @@ -942,6 +942,7 @@ class DocumentFragment : Fragment(), DocumentLoader.Listener { freshOpenPending = false ReviewInvitation.recordDocumentOpen(activity) + (activity as? MainActivity)?.onDocumentShown() } } 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 b3d7afbd0b9e..65e6c266db7d 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 @@ -102,14 +102,33 @@ class MainActivity : AppCompatActivity() { // an externally opened document leaves the app rather than the document, and // carries unsaved edits out just the same confirmLeavingEdits { - // fall through to the default behavior (close the activity) - isEnabled = false - onBackPressedDispatcher.onBackPressed() - isEnabled = true + // the only moment a document opened from another app is put down again + val asked = + documentFragment != null && + preparedReview?.showIfReady( + onAsked = { ReviewInvitation.recordAsk(this@MainActivity) }, + onDone = { leaveApp() }, + ) == true + preparedReview = null + + if (!asked) { + leaveApp() + } } } } + /** Falls through to the default back behavior, which closes the activity. */ + private fun leaveApp() { + if (isFinishing || isDestroyed) { + return + } + + backCallback.isEnabled = false + onBackPressedDispatcher.onBackPressed() + backCallback.isEnabled = true + } + // kept because onPause has to stop it private var ttsActionMode: TtsActionModeCallback? = null @@ -145,6 +164,9 @@ class MainActivity : AppCompatActivity() { // lands would pass isEarned again. never reset: one hand-off per activity is plenty private var reviewRequested = false + // fetched while an externally opened document is read: back must not wait on the network + private var preparedReview: InAppReview.Prepared? = null + /** * Loads and saves the open document. Scoped to the activity, so it survives a configuration * change and [DocumentFragment] finds it already there rather than waiting for a binding. @@ -870,6 +892,16 @@ class MainActivity : AppCompatActivity() { .show() } + /** Called once a fresh open has rendered. */ + fun onDocumentShown() { + if (!documentOpenedExternally || reviewRequested || !ReviewInvitation.isEarned(this)) { + return + } + + reviewRequested = true + preparedReview = InAppReview.prepare(this, analyticsManager) + } + /** Deliberately not from [closeFailedDocument], the last moment on earth to ask for stars. */ private fun askForReviewIfEarned() { if (reviewRequested || !ReviewInvitation.isEarned(this)) { diff --git a/app/src/noReview/java/app/opendocument/droid/nonfree/InAppReview.kt b/app/src/noReview/java/app/opendocument/droid/nonfree/InAppReview.kt index 3b4426e2960e..b21981c7be15 100644 --- a/app/src/noReview/java/app/opendocument/droid/nonfree/InAppReview.kt +++ b/app/src/noReview/java/app/opendocument/droid/nonfree/InAppReview.kt @@ -6,4 +6,11 @@ import android.app.Activity object InAppReview { fun request(activity: Activity, analyticsManager: AnalyticsManager, onAsked: () -> Unit) {} + + fun prepare(activity: Activity, analyticsManager: AnalyticsManager): Prepared = Prepared() + + class Prepared { + + fun showIfReady(onAsked: () -> Unit, onDone: () -> Unit): Boolean = false + } } diff --git a/app/src/review/java/app/opendocument/droid/nonfree/InAppReview.kt b/app/src/review/java/app/opendocument/droid/nonfree/InAppReview.kt index 7a14ade6a3e7..b354b6f90ec9 100644 --- a/app/src/review/java/app/opendocument/droid/nonfree/InAppReview.kt +++ b/app/src/review/java/app/opendocument/droid/nonfree/InAppReview.kt @@ -1,6 +1,7 @@ package app.opendocument.droid.nonfree import android.app.Activity +import com.google.android.play.core.review.ReviewInfo import com.google.android.play.core.review.ReviewManagerFactory /** @@ -34,4 +35,47 @@ object InAppReview { } } } + + /** + * Fetches the sheet ahead of the moment to show it, so that moment does not wait on the + * network. + */ + fun prepare(activity: Activity, analyticsManager: AnalyticsManager): Prepared { + val prepared = Prepared(activity, analyticsManager) + analyticsManager.report("in_app_review_eligible") + + prepared.manager.requestReviewFlow().addOnCompleteListener { task -> + if (task.isSuccessful) { + prepared.reviewInfo = task.result + } else { + analyticsManager.report("in_app_review_error") + } + } + + return prepared + } + + class Prepared + internal constructor( + private val activity: Activity, + private val analyticsManager: AnalyticsManager, + ) { + internal val manager = ReviewManagerFactory.create(activity) + internal var reviewInfo: ReviewInfo? = null + + /** False when the sheet has not arrived yet - then [onDone] does not run. */ + fun showIfReady(onAsked: () -> Unit, onDone: () -> Unit): Boolean { + val reviewInfo = reviewInfo ?: return false + + analyticsManager.report("in_app_review_start") + onAsked() + + manager.launchReviewFlow(activity, reviewInfo).addOnCompleteListener { + analyticsManager.report("in_app_review_done") + onDone() + } + + return true + } + } }