@@ -7,69 +7,101 @@ part 'app_review_config.g.dart';
77/// {@template app_review_config}
88/// Defines the remote configuration for the two-layer App Review Funnel.
99///
10- /// This system strategically prompts engaged users for feedback to maximize
11- /// positive public reviews while capturing constructive criticism privately.
10+ /// This system strategically prompts engaged users for feedback to maximize positive
11+ /// public reviews while capturing constructive criticism privately. It uses a
12+ /// combination of this configuration, the `UserFeedDecoratorStatus` model, and
13+ /// the `AppReview` model to manage the user's journey.
1214///
13- /// ### How It Works
15+ /// ### Architectural Workflow
1416///
15- /// 1. **Trigger**: A user becomes eligible to see the prompt after reaching
16- /// the [positiveInteractionThreshold] of positive actions (e.g., saves).
17+ /// 1. **Eligibility**: A user becomes eligible to see the internal prompt after
18+ /// reaching the [positiveInteractionThreshold] of positive actions (e.g.,
19+ /// saving headlines).
1720///
18- /// 2. **Prompt**: The `FeedDecoratorType.rateApp` decorator asks the user
19- /// "Are you enjoying the app?". The display logic is managed by the user's
20- /// `UserFeedDecoratorStatus` for `rateApp`, which respects the
21- /// [initialPromptCooldownDays].
21+ /// 2. **Display Logic**: The `FeedDecoratorType.rateApp` decorator's visibility
22+ /// is controlled by the user's `UserFeedDecoratorStatus` for `rateApp`. The
23+ /// decorator is only shown if `isCompleted` is `false` and the cooldown
24+ /// period (defined here as [initialPromptCooldownDays]) has passed since
25+ /// `lastShownAt`.
2226///
23- /// 3. **Action**:
24- /// - **On "Yes"**: The client sets `isCompleted` to `true` on the user's
25- /// `UserFeedDecoratorStatus` for `rateApp` and immediately triggers the
26- /// native OS in-app review dialog if applicable ie the app is hosted in
27- /// google play or apple store. The prompt will not be shown again.
28- /// - **On "No"**: The client only updates the `lastShownAt` timestamp on
29- /// the status object. The prompt will not be shown again until the
30- /// cooldown period has passed. No public review is requested.
27+ /// 3. **User Interaction & State Change**:
28+ /// - **On "Yes" (Positive Feedback)**:
29+ /// - An `AppReview` record is created/updated with `initialFeedback: positive`
30+ /// and `wasStoreReviewRequested` is set to `true`.
31+ /// - The native OS in-app review dialog is immediately triggered. This is a
32+ /// "fire-and-forget" action; the OS controls if the dialog appears and
33+ /// provides no feedback to the app.
34+ /// - The `UserFeedDecoratorStatus` for `rateApp` has its `isCompleted` flag
35+ /// set to `true`, **permanently preventing the internal prompt from
36+ /// appearing again for this user.**
37+ ///
38+ /// - **On "No" (Negative Feedback)**:
39+ /// - An `AppReview` record is created/updated with `initialFeedback: negative`.
40+ /// The app may optionally collect a reason, which is stored in the
41+ /// `negativeFeedbackHistory`.
42+ /// - The `UserFeedDecoratorStatus` for `rateApp` only has its `lastShownAt`
43+ /// timestamp updated. `isCompleted` remains `false`.
44+ /// - The prompt will not be shown again until the cooldown period has
45+ /// passed, at which point the user may be asked again.
3146/// {@endtemplate}
3247@immutable
3348@JsonSerializable (explicitToJson: true , includeIfNull: true , checked: true )
3449class AppReviewConfig extends Equatable {
3550 /// {@macro app_review_config}
3651 const AppReviewConfig ({
52+ required this .enabled,
3753 required this .positiveInteractionThreshold,
3854 required this .initialPromptCooldownDays,
55+ required this .isNegativeFeedbackFollowUpEnabled,
3956 });
4057
4158 /// Creates a [AppReviewConfig] from JSON data.
4259 factory AppReviewConfig .fromJson (Map <String , dynamic > json) =>
4360 _$AppReviewConfigFromJson (json);
4461
62+ /// A master switch to enable or disable the entire app review funnel.
63+ final bool enabled;
64+
4565 /// The number of positive interactions (e.g., saving a headline) required
4666 /// to trigger the initial review prompt.
4767 final int positiveInteractionThreshold;
4868
4969 /// The number of days to wait before showing the initial prompt again if the
50- /// user dismisses it .
70+ /// user provides negative feedback .
5171 final int initialPromptCooldownDays;
5272
73+ /// A switch to enable or disable the follow-up prompt that asks for a
74+ /// text reason after a user provides negative feedback.
75+ final bool isNegativeFeedbackFollowUpEnabled;
76+
5377 /// Converts this [AppReviewConfig] instance to JSON data.
5478 Map <String , dynamic > toJson () => _$AppReviewConfigToJson (this );
5579
5680 @override
5781 List <Object > get props => [
82+ enabled,
5883 positiveInteractionThreshold,
5984 initialPromptCooldownDays,
85+ isNegativeFeedbackFollowUpEnabled,
6086 ];
6187
6288 /// Creates a copy of this [AppReviewConfig] but with the given fields
6389 /// replaced with the new values.
6490 AppReviewConfig copyWith ({
91+ bool ? enabled,
6592 int ? positiveInteractionThreshold,
6693 int ? initialPromptCooldownDays,
94+ bool ? isNegativeFeedbackFollowUpEnabled,
6795 }) {
6896 return AppReviewConfig (
97+ enabled: enabled ?? this .enabled,
6998 positiveInteractionThreshold:
7099 positiveInteractionThreshold ?? this .positiveInteractionThreshold,
71100 initialPromptCooldownDays:
72101 initialPromptCooldownDays ?? this .initialPromptCooldownDays,
102+ isNegativeFeedbackFollowUpEnabled:
103+ isNegativeFeedbackFollowUpEnabled ??
104+ this .isNegativeFeedbackFollowUpEnabled,
73105 );
74106 }
75107}
0 commit comments