Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 84 additions & 11 deletions lib/app_configuration/widgets/app_review_settings_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class AppReviewSettingsForm extends StatefulWidget {
}

class _AppReviewSettingsFormState extends State<AppReviewSettingsForm> {
late final TextEditingController _positiveInteractionThresholdController;
late final TextEditingController _interactionCycleThresholdController;
late final TextEditingController _initialPromptCooldownController;

@override
Expand All @@ -46,8 +46,8 @@ class _AppReviewSettingsFormState extends State<AppReviewSettingsForm> {

void _initializeControllers() {
final appReviewConfig = widget.remoteConfig.features.community.appReview;
_positiveInteractionThresholdController = TextEditingController(
text: appReviewConfig.positiveInteractionThreshold.toString(),
_interactionCycleThresholdController = TextEditingController(
text: appReviewConfig.interactionCycleThreshold.toString(),
);
_initialPromptCooldownController = TextEditingController(
text: appReviewConfig.initialPromptCooldownDays.toString(),
Expand All @@ -56,8 +56,8 @@ class _AppReviewSettingsFormState extends State<AppReviewSettingsForm> {

void _updateControllers() {
final appReviewConfig = widget.remoteConfig.features.community.appReview;
_positiveInteractionThresholdController.text = appReviewConfig
.positiveInteractionThreshold
_interactionCycleThresholdController.text = appReviewConfig
.interactionCycleThreshold
.toString();
_initialPromptCooldownController.text = appReviewConfig
.initialPromptCooldownDays
Expand All @@ -66,7 +66,7 @@ class _AppReviewSettingsFormState extends State<AppReviewSettingsForm> {

@override
void dispose() {
_positiveInteractionThresholdController.dispose();
_interactionCycleThresholdController.dispose();
_initialPromptCooldownController.dispose();
super.dispose();
}
Expand Down Expand Up @@ -121,14 +121,14 @@ class _AppReviewSettingsFormState extends State<AppReviewSettingsForm> {
expandedCrossAxisAlignment: CrossAxisAlignment.start,
children: [
AppConfigIntField(
label: l10n.positiveInteractionThresholdLabel,
label: l10n.interactionCycleThresholdLabel,
description:
l10n.positiveInteractionThresholdDescription,
value: appReviewConfig.positiveInteractionThreshold,
l10n.interactionCycleThresholdDescription,
value: appReviewConfig.interactionCycleThreshold,
onChanged: (value) {
final newConfig = communityConfig.copyWith(
appReview: appReviewConfig.copyWith(
positiveInteractionThreshold: value,
interactionCycleThreshold: value,
),
);
widget.onConfigChanged(
Expand All @@ -140,7 +140,7 @@ class _AppReviewSettingsFormState extends State<AppReviewSettingsForm> {
),
);
},
controller: _positiveInteractionThresholdController,
controller: _interactionCycleThresholdController,
),
AppConfigIntField(
label: l10n.initialPromptCooldownLabel,
Expand Down Expand Up @@ -168,6 +168,63 @@ class _AppReviewSettingsFormState extends State<AppReviewSettingsForm> {
},
),
),
Padding(
padding: const EdgeInsetsDirectional.only(
start: AppSpacing.lg,
),
child: LayoutBuilder(
builder: (context, constraints) {
final isMobile = constraints.maxWidth < 600;
return ExpansionTile(
title: Text(l10n.eligiblePositiveInteractionsTitle),
initiallyExpanded: !isMobile,
childrenPadding: const EdgeInsetsDirectional.only(
start: AppSpacing.lg,
top: AppSpacing.md,
bottom: AppSpacing.md,
),
expandedCrossAxisAlignment: CrossAxisAlignment.start,
children: [
...PositiveInteractionType.values.map(
(interactionType) => SwitchListTile(
title: Text(interactionType.l10n(context)),
value: appReviewConfig
.eligiblePositiveInteractions
.contains(interactionType),
onChanged: (value) {
final currentInteractions =
List<PositiveInteractionType>.from(
appReviewConfig
.eligiblePositiveInteractions,
);
if (value) {
currentInteractions.add(interactionType);
} else {
currentInteractions.remove(interactionType);
}
final newAppReviewConfig = appReviewConfig
.copyWith(
eligiblePositiveInteractions:
currentInteractions,
);
widget.onConfigChanged(
widget.remoteConfig.copyWith(
features: widget.remoteConfig.features
.copyWith(
community: communityConfig.copyWith(
appReview: newAppReviewConfig,
),
),
),
);
},
),
),
],
);
},
),
),
Padding(
padding: const EdgeInsetsDirectional.only(
start: AppSpacing.lg,
Expand Down Expand Up @@ -244,3 +301,19 @@ class _AppReviewSettingsFormState extends State<AppReviewSettingsForm> {
);
}
}

extension on PositiveInteractionType {
String l10n(BuildContext context) {
final l10n = AppLocalizationsX(context).l10n;
switch (this) {
case PositiveInteractionType.saveItem:
return l10n.positiveInteractionTypeSaveItem;
case PositiveInteractionType.followItem:
return l10n.positiveInteractionTypeFollowItem;
case PositiveInteractionType.shareContent:
return l10n.positiveInteractionTypeShareContent;
case PositiveInteractionType.saveFilter:
return l10n.positiveInteractionTypeSaveFilter;
}
}
}
49 changes: 40 additions & 9 deletions lib/community_management/view/engagements_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,26 @@ class _EngagementsDataSource extends DataTableSource {
return DataRow2(
cells: [
DataCell(
Chip(
label: Text(engagement.reaction.reactionType.name),
backgroundColor: _getReactionColor(
context,
engagement.reaction.reactionType,
),
side: BorderSide.none,
visualDensity: VisualDensity.compact,
),
engagement.reaction != null
? Chip(
label: Text(
engagement.reaction!.reactionType.l10n(context),
),
backgroundColor: _getReactionColor(
context,
engagement.reaction!.reactionType,
),
side: BorderSide.none,
visualDensity: VisualDensity.compact,
)
: Text(
l10n.notAvailable,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
),
DataCell(
Text(DateFormat('dd-MM-yyyy').format(engagement.createdAt.toLocal())),
Expand Down Expand Up @@ -243,3 +254,23 @@ class _EngagementsDataSource extends DataTableSource {
}
}
}

extension on ReactionType {
String l10n(BuildContext context) {
final l10n = AppLocalizationsX(context).l10n;
switch (this) {
case ReactionType.like:
return l10n.reactionTypeLike;
case ReactionType.insightful:
return l10n.reactionTypeInsightful;
case ReactionType.amusing:
return l10n.reactionTypeAmusing;
case ReactionType.sad:
return l10n.reactionTypeSad;
case ReactionType.angry:
return l10n.reactionTypeAngry;
case ReactionType.skeptical:
return l10n.reactionTypeSkeptical;
}
}
}
2 changes: 1 addition & 1 deletion lib/community_management/view/reports_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ class _ReportsDataSource extends DataTableSource {
return colorScheme.primaryContainer.withOpacity(0.5);
case ReportableEntity.source:
return colorScheme.secondaryContainer.withOpacity(0.5);
case ReportableEntity.engagement:
case ReportableEntity.comment:
return colorScheme.tertiaryContainer.withOpacity(0.5);
}
}
Expand Down
42 changes: 36 additions & 6 deletions lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3272,17 +3272,17 @@ abstract class AppLocalizations {
/// **'Activates the internal system that periodically asks users if they are enjoying the app.'**
String get enableAppFeedbackSystemDescription;

/// Label for the positive interaction threshold input field.
/// Label for the interaction cycle threshold input field.
///
/// In en, this message translates to:
/// **'Positive Interaction Threshold'**
String get positiveInteractionThresholdLabel;
/// **'Interaction Cycle Threshold'**
String get interactionCycleThresholdLabel;

/// Description for the positive interaction threshold input field.
/// Description for the interaction cycle threshold input field.
///
/// In en, this message translates to:
/// **'Defines the number of positive actions (e.g., save, like) required to trigger the enjoyment prompt. The prompt is shown each time the user\'s total positive actions is a multiple of this number.'**
String get positiveInteractionThresholdDescription;
String get interactionCycleThresholdDescription;

/// Label for the initial prompt cooldown input field.
///
Expand All @@ -3293,7 +3293,7 @@ abstract class AppLocalizations {
/// Description for the initial prompt cooldown input field.
///
/// In en, this message translates to:
/// **'If a user dismisses the prompt, wait this many days before they are eligible to see it again. Note: The \'Rate App\' decorator in the Feed section controls its own separate display frequency.'**
/// **'The number of days to wait before showing the enjoyment prompt for the first time, This cooldown ensures users are not asked until they used the app enough.'**
String get initialPromptCooldownDescription;

/// Label for the switch to request a store review after positive feedback.
Expand Down Expand Up @@ -3326,6 +3326,36 @@ abstract class AppLocalizations {
/// **'Internal Prompt Logic'**
String get internalPromptLogicTitle;

/// Title for the expansion tile for eligible positive interactions.
///
/// In en, this message translates to:
/// **'Eligible Positive Interactions'**
String get eligiblePositiveInteractionsTitle;

/// Label for the 'save item' positive interaction type.
///
/// In en, this message translates to:
/// **'Save a content item (e.g., a headline)'**
String get positiveInteractionTypeSaveItem;

/// Label for the 'follow item' positive interaction type.
///
/// In en, this message translates to:
/// **'Follow an entity (e.g., a topic, source, or country)'**
String get positiveInteractionTypeFollowItem;

/// Label for the 'share content' positive interaction type.
///
/// In en, this message translates to:
/// **'Share a content item (e.g., a headline)'**
String get positiveInteractionTypeShareContent;

/// Label for the 'save filter' positive interaction type.
///
/// In en, this message translates to:
/// **'Create a saved filter'**
String get positiveInteractionTypeSaveFilter;

/// Title for the nested expansion tile for app review follow-up actions.
///
/// In en, this message translates to:
Expand Down
24 changes: 21 additions & 3 deletions lib/l10n/app_localizations_ar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1763,18 +1763,18 @@ class AppLocalizationsAr extends AppLocalizations {
'ينشط النظام الداخلي الذي يسأل المستخدمين بشكل دوري عما إذا كانوا يستمتعون بالتطبيق.';

@override
String get positiveInteractionThresholdLabel => 'عتبة التفاعل الإيجابي';
String get interactionCycleThresholdLabel => 'عتبة دورة التفاعل';

@override
String get positiveInteractionThresholdDescription =>
String get interactionCycleThresholdDescription =>
'يحدد عدد الإجراءات الإيجابية (مثل الحفظ، الإعجاب) المطلوبة لتشغيل موجه الاستمتاع. يظهر الموجه في كل مرة يكون فيها إجمالي الإجراءات الإيجابية للمستخدم من مضاعفات هذا الرقم.';

@override
String get initialPromptCooldownLabel => 'فترة تهدئة الموجه الأولي (أيام)';

@override
String get initialPromptCooldownDescription =>
'إذا رفض المستخدم الموجه، انتظر هذا العدد من الأيام قبل أن يكون مؤهلاً لرؤيته مرة أخرى. ملاحظة: تتحكم زينة \'تقييم التطبيق\' في قسم الموجز في تردد عرضها المنفصل.';
'عدد الأيام التي يجب الانتظار فيها قبل إظهار مطالبة الاستمتاع للمرة الأولى. تضمن فترة التهدئة هذه عدم سؤال المستخدمين حتى يستخدموا التطبيق بما فيه الكفاية.';

@override
String get requestStoreReviewLabel => 'طلب مراجعة المتجر بعد \'نعم\'';
Expand All @@ -1793,6 +1793,24 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get internalPromptLogicTitle => 'منطق الموجه الداخلي';

@override
String get eligiblePositiveInteractionsTitle => 'التفاعلات الإيجابية المؤهلة';

@override
String get positiveInteractionTypeSaveItem =>
'حفظ عنصر محتوى (مثل عنوان رئيسي)';

@override
String get positiveInteractionTypeFollowItem =>
'متابعة كيان (مثل موضوع أو مصدر أو بلد)';

@override
String get positiveInteractionTypeShareContent =>
'مشاركة عنصر محتوى (مثل عنوان رئيسي)';

@override
String get positiveInteractionTypeSaveFilter => 'إنشاء مرشح محفوظ';

@override
String get followUpActionsTitle => 'إجراءات المتابعة';

Expand Down
26 changes: 22 additions & 4 deletions lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1766,19 +1766,18 @@ class AppLocalizationsEn extends AppLocalizations {
'Activates the internal system that periodically asks users if they are enjoying the app.';

@override
String get positiveInteractionThresholdLabel =>
'Positive Interaction Threshold';
String get interactionCycleThresholdLabel => 'Interaction Cycle Threshold';

@override
String get positiveInteractionThresholdDescription =>
String get interactionCycleThresholdDescription =>
'Defines the number of positive actions (e.g., save, like) required to trigger the enjoyment prompt. The prompt is shown each time the user\'s total positive actions is a multiple of this number.';

@override
String get initialPromptCooldownLabel => 'Initial Prompt Cooldown (Days)';

@override
String get initialPromptCooldownDescription =>
'If a user dismisses the prompt, wait this many days before they are eligible to see it again. Note: The \'Rate App\' decorator in the Feed section controls its own separate display frequency.';
'The number of days to wait before showing the enjoyment prompt for the first time, This cooldown ensures users are not asked until they used the app enough.';

@override
String get requestStoreReviewLabel => 'Request Store Review After \'Yes\'';
Expand All @@ -1798,6 +1797,25 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get internalPromptLogicTitle => 'Internal Prompt Logic';

@override
String get eligiblePositiveInteractionsTitle =>
'Eligible Positive Interactions';

@override
String get positiveInteractionTypeSaveItem =>
'Save a content item (e.g., a headline)';

@override
String get positiveInteractionTypeFollowItem =>
'Follow an entity (e.g., a topic, source, or country)';

@override
String get positiveInteractionTypeShareContent =>
'Share a content item (e.g., a headline)';

@override
String get positiveInteractionTypeSaveFilter => 'Create a saved filter';

@override
String get followUpActionsTitle => 'Follow-up Actions';

Expand Down
Loading
Loading