Review prompt tras check-in: ≥2 meses de histórico, una vez por versión (build 85) #41
- ReviewRequestService: gate por meses distintos con snapshots + versión - QuickUpdateView.saveAll y AddSnapshotView.performSave (solo alta, no edición) piden reseña via \.requestReview tras cerrar el sheet - Motivo: 0 ratings visibles en US, 1 en ES — la ficha no convierte Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VXtwQP6nYnbLFVqsHiTK5G
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
/// Decides when to ask for an App Store rating: after a "positive moment"
|
||||
/// (a saved check-in), only once the habit is established (≥2 distinct months
|
||||
/// of snapshots) and at most once per app version. Apple additionally caps
|
||||
/// the system prompt at 3 displays per year, so over-calling is safe.
|
||||
///
|
||||
/// Call sites own the actual prompt via SwiftUI's `\.requestReview` — this
|
||||
/// service only answers "should we ask now?" and records that we did.
|
||||
@MainActor
|
||||
enum ReviewRequestService {
|
||||
private static let lastVersionKey = "reviewRequest.lastVersion"
|
||||
|
||||
/// Returns true at most once per app version, and only when the user has
|
||||
/// snapshots in 2+ distinct months (they've seen the app deliver value).
|
||||
/// Marks the version as asked when returning true.
|
||||
static func shouldRequestAfterCheckIn() -> Bool {
|
||||
guard distinctSnapshotMonths() >= 2 else { return false }
|
||||
|
||||
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "unknown"
|
||||
let defaults = UserDefaults.standard
|
||||
guard defaults.string(forKey: lastVersionKey) != version else { return false }
|
||||
|
||||
defaults.set(version, forKey: lastVersionKey)
|
||||
return true
|
||||
}
|
||||
|
||||
private static func distinctSnapshotMonths() -> Int {
|
||||
let request = NSFetchRequest<Snapshot>(entityName: "Snapshot")
|
||||
guard let snapshots = try? CoreDataStack.shared.viewContext.fetch(request) else { return 0 }
|
||||
let calendar = Calendar.current
|
||||
let months = Set(snapshots.map { snapshot in
|
||||
calendar.dateComponents([.year, .month], from: snapshot.date)
|
||||
})
|
||||
return months.count
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,11 @@ import CoreData
|
||||
import TipKit
|
||||
import UIKit
|
||||
import PhotosUI
|
||||
import StoreKit
|
||||
|
||||
struct QuickUpdateView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.requestReview) private var requestReview
|
||||
@Environment(\.managedObjectContext) private var context
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
@FetchRequest(
|
||||
@@ -375,6 +377,11 @@ struct QuickUpdateView: View {
|
||||
}
|
||||
// Keep the share-extension mirror in sync with what was just saved
|
||||
SharedQuickUpdateSync.refreshMirror()
|
||||
// Positive moment: check-in saved. Ask for a rating once the sheet
|
||||
// is gone (gated to ≥2 months of history, once per version).
|
||||
if ReviewRequestService.shouldRequestAfterCheckIn() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) { requestReview() }
|
||||
}
|
||||
dismiss()
|
||||
} catch {
|
||||
saveError = error.localizedDescription
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import StoreKit
|
||||
|
||||
struct AddSourceView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@@ -415,6 +416,7 @@ struct EditSourceView: View {
|
||||
struct AddSnapshotView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
@Environment(\.requestReview) private var requestReview
|
||||
let source: InvestmentSource
|
||||
let snapshot: Snapshot?
|
||||
|
||||
@@ -648,6 +650,12 @@ struct AddSnapshotView: View {
|
||||
return
|
||||
}
|
||||
|
||||
// Positive moment: check-in saved (new snapshots only, not edits).
|
||||
// Gated to ≥2 months of history, once per version.
|
||||
if !isEdit, ReviewRequestService.shouldRequestAfterCheckIn() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) { requestReview() }
|
||||
}
|
||||
|
||||
dismiss()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user