// lib/services/review_service.dart import '../data/save_repository.dart'; import 'review_prompt_policy.dart'; /// A thin seam over the in_app_review plugin so unit tests never reach platform /// channels. The release wiring uses [StoreReviewer] (see store_reviewer.dart), /// which forwards to the real InAppReview instance. abstract class Reviewer { Future isAvailable(); Future requestReview(); } /// Decides and triggers the native "rate this app" sheet at most once, on a /// genuine high point. Holds no plugin dependency — that lives behind /// [Reviewer] — so the decision path is fully unit-testable. class ReviewService { ReviewService({ required SaveRepository save, required Reviewer reviewer, ReviewPromptPolicy policy = const ReviewPromptPolicy(), }) : _save = save, _reviewer = reviewer, _policy = policy; final SaveRepository _save; final Reviewer _reviewer; final ReviewPromptPolicy _policy; /// Call right after a stage win has been recorded to progress. Asks for a /// review if the policy allows and the store can show the sheet, then never /// again. The one-shot flag is only burned once the sheet is actually /// requested, so an unavailable store retries on a later win. Every failure /// is swallowed — a review prompt must never break gameplay. Future maybeRequestAfterWin({required int stars}) async { final allowed = _policy.shouldRequest( alreadyRequested: _save.reviewRequested, won: true, stars: stars, totalStagesWon: _save.stagesClearedCount, ); if (!allowed) return; try { if (await _reviewer.isAvailable()) { await _reviewer.requestReview(); await _save.markReviewRequested(); } } catch (_) {/* never break gameplay over a review prompt */} } }