From 022e3098739baf8ec42ef2f707efc8d3208759ce Mon Sep 17 00:00:00 2001 From: Stefan Lange-Hegermann Date: Mon, 29 Jun 2026 14:55:09 +0200 Subject: [PATCH] Show rating prompt after share sheet is dismissed iOS: moved registerSuccessfulExport() into onDismiss so it fires after the share sheet closes, not while it is still open. Android: hold launchReview() until the activity is RESUMED so the dialog cannot overlap the share chooser. Co-Authored-By: Claude Sonnet 4.6 --- Cable/Loads/LoadsView.swift | 7 ++++--- Cable/Systems/SystemBillOfMaterialsView.swift | 6 ++++-- .../src/main/java/app/voltplan/cable/data/ReviewPrompt.kt | 8 ++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Cable/Loads/LoadsView.swift b/Cable/Loads/LoadsView.swift index d396b16..af7daf0 100644 --- a/Cable/Loads/LoadsView.swift +++ b/Cable/Loads/LoadsView.swift @@ -219,7 +219,10 @@ struct LoadsView: View { } ) } - .sheet(item: $overviewShareItem, onDismiss: cleanupOverviewShareItem) { item in + .sheet(item: $overviewShareItem, onDismiss: { + cleanupOverviewShareItem() + ReviewPrompt.registerSuccessfulExport() + }) { item in ShareSheet(items: item.shareItems) } .alert( @@ -1133,7 +1136,6 @@ struct LoadsView: View { await MainActor.run { overviewShareItem = OverviewShareItem(shareItems: [url], tempURL: url) isExportingOverview = false - ReviewPrompt.registerSuccessfulExport() } } catch { await MainActor.run { @@ -1163,7 +1165,6 @@ struct LoadsView: View { "system": snapshot.systemName, ]) overviewShareItem = OverviewShareItem(shareItems: [url], tempURL: url) - ReviewPrompt.registerSuccessfulExport() } else { overviewExportError = OverviewExportError( message: String(localized: "overview.share.diagram.error", defaultValue: "Could not generate diagram. Check your internet connection.") diff --git a/Cable/Systems/SystemBillOfMaterialsView.swift b/Cable/Systems/SystemBillOfMaterialsView.swift index e7bf9ee..61c6570 100644 --- a/Cable/Systems/SystemBillOfMaterialsView.swift +++ b/Cable/Systems/SystemBillOfMaterialsView.swift @@ -301,7 +301,10 @@ struct SystemBillOfMaterialsView: View { } } .accessibilityIdentifier("system-bom-view") - .sheet(item: $activeShareItem, onDismiss: cleanupShareItem) { item in + .sheet(item: $activeShareItem, onDismiss: { + cleanupShareItem() + ReviewPrompt.registerSuccessfulExport() + }) { item in ShareSheet(items: [item.url]) } .alert(item: $exportError) { error in @@ -346,7 +349,6 @@ struct SystemBillOfMaterialsView: View { ) await MainActor.run { activeShareItem = ExportedPDFShareItem(url: url) - ReviewPrompt.registerSuccessfulExport() } } catch { await MainActor.run { diff --git a/android/app/src/main/java/app/voltplan/cable/data/ReviewPrompt.kt b/android/app/src/main/java/app/voltplan/cable/data/ReviewPrompt.kt index 8e186e8..8232c48 100644 --- a/android/app/src/main/java/app/voltplan/cable/data/ReviewPrompt.kt +++ b/android/app/src/main/java/app/voltplan/cable/data/ReviewPrompt.kt @@ -7,11 +7,14 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner import app.voltplan.cable.BuildConfig import app.voltplan.cable.analytics.Analytics import com.google.android.play.core.ktx.launchReview import com.google.android.play.core.ktx.requestReview import com.google.android.play.core.review.ReviewManagerFactory +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first /** @@ -113,6 +116,11 @@ object ReviewPrompt { runCatching { val manager = ReviewManagerFactory.create(context) val reviewInfo = manager.requestReview() + // Wait until the activity is resumed so the dialog doesn't overlap a share sheet + // that was just launched (startActivity returns immediately, so we may still be paused). + (activity as? LifecycleOwner)?.lifecycle?.currentStateFlow + ?.filter { state: Lifecycle.State -> state.isAtLeast(Lifecycle.State.RESUMED) } + ?.first() manager.launchReview(activity, reviewInfo) } }