diff --git a/README.md b/README.md
index 2d93376f..9f12ed7d 100644
--- a/README.md
+++ b/README.md
@@ -59,24 +59,16 @@ Directly manage all user-generated content from a centralized command center. Re
-⚙️ App Monetization & Remote Control
-
-### 💸 Centralized Monetization Engine
-Take direct control of your mobile app's revenue strategy. This integrated system allows you to manage your ad inventory and fine-tune display rules without ever touching the mobile app's code.
-- **Flexible Ad Provider Strategy:** Instantly switch the mobile app's primary ad source between industry-standard networks like Google AdMob for maximum control.
-- **Granular, Role-Based Rules:** Define precisely how and when ads are shown based on user subscription tiers (e.g., guest, standard, premium), optimizing both revenue and user experience.
-> **Your Advantage:** Deploy a powerful, backend-driven monetization strategy. A/B test ad providers and adjust revenue models on the fly to respond to market demands in real-time.
-
----
-
-### 🚀 Real-Time Application Management
-Dynamically control the mobile app's behavior and operational state directly from the dashboard, eliminating the need for constant app store updates.
-- **Critical State Management:** Instantly activate a maintenance mode or enforce a mandatory app update for your users to handle operational issues or critical releases gracefully.
-- **Dynamic In-App Content:** Remotely manage the visibility and behavior of in-feed promotional prompts and user engagement elements.
-- **Tier-Based Feature Gating:** Define and enforce feature limits based on user roles, such as setting the maximum number of followed topics or saved headlines for different subscription levels.
-- **Full Community Feature Control:** Remotely enable or disable the entire user engagement system (reactions, comments), the content reporting feature, and the in-app review funnel. Fine-tune engagement modes and configure rules for when and how users are prompted for feedback.
-- **Global Notification Control:** Remotely enable or disable the entire push notification system, switch between providers (e.g., Firebase, OneSignal), and toggle specific delivery types like breaking news or daily digests.
-> **Your Advantage:** Gain unparalleled agility to manage your live application. Ensure service stability, drive user actions, and configure business rules instantly, all from a centralized control panel.
+⚙️ Real-Time App Configuration & Remote Control
+
+### 🚀 Dynamic Application Control
+Dynamically control the mobile app's behavior, features, and operational state directly from the dashboard, eliminating the need for constant app store updates.
+- **App Monetization:** Take direct control of your mobile app's revenue. Remotely manage ad providers like Google AdMob and define granular, role-based display rules to optimize revenue and user experience.
+- **Feature Toggling:** Instantly enable or disable major features. Globally toggle the entire push notification system, the community engagement suite (reactions, comments), the content reporting feature, and the in-app review funnel.
+- **Operational Control:** Ensure stability and manage releases gracefully. Activate a maintenance mode or enforce a mandatory app update for all users directly from the dashboard.
+- **Content & Access Control:** Define and enforce feature limits based on user subscription tiers, such as setting the maximum number of saved articles. Remotely manage the visibility and behavior of in-feed promotional prompts.
+- **Analytics & Data Insights:** Gain critical insights by remotely configuring the analytics system. Enable or disable the entire system, switch between providers like Firebase or Mixpanel, and fine-tune event tracking and sampling rates to understand user behavior without deploying new app versions.
+> **Your Advantage:** Gain unparalleled agility to manage your live application. Ensure service stability, drive user actions, configure business rules, and adjust your data strategy instantly, all from a single, centralized control panel.
diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart
index a724e64b..736ffa56 100644
--- a/lib/app/view/app.dart
+++ b/lib/app/view/app.dart
@@ -18,7 +18,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/content_manageme
import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/sources_filter/sources_filter_bloc.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/topics_filter/topics_filter_bloc.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
-import 'package:flutter_news_app_web_dashboard_full_source_code/overview/bloc/overview_bloc.dart';
+// import 'package:flutter_news_app_web_dashboard_full_source_code/overview/bloc/overview_bloc.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/router/router.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/services/pending_deletions_service.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/services/pending_updates_service.dart';
@@ -40,7 +40,6 @@ class App extends StatelessWidget {
required DataRepository
userContentPreferencesRepository,
required DataRepository remoteConfigRepository,
- required DataRepository dashboardSummaryRepository,
required DataRepository countriesRepository,
required DataRepository languagesRepository,
required DataRepository usersRepository,
@@ -59,7 +58,6 @@ class App extends StatelessWidget {
_userContentPreferencesRepository = userContentPreferencesRepository,
_remoteConfigRepository = remoteConfigRepository,
_kvStorageService = storageService,
- _dashboardSummaryRepository = dashboardSummaryRepository,
_countriesRepository = countriesRepository,
_languagesRepository = languagesRepository,
_usersRepository = usersRepository,
@@ -77,7 +75,6 @@ class App extends StatelessWidget {
final DataRepository
_userContentPreferencesRepository;
final DataRepository _remoteConfigRepository;
- final DataRepository _dashboardSummaryRepository;
final DataRepository _countriesRepository;
final DataRepository _languagesRepository;
final DataRepository _usersRepository;
@@ -101,7 +98,6 @@ class App extends StatelessWidget {
RepositoryProvider.value(value: _appSettingsRepository),
RepositoryProvider.value(value: _userContentPreferencesRepository),
RepositoryProvider.value(value: _remoteConfigRepository),
- RepositoryProvider.value(value: _dashboardSummaryRepository),
RepositoryProvider.value(value: _countriesRepository),
RepositoryProvider.value(value: _languagesRepository),
RepositoryProvider.value(value: _usersRepository),
@@ -163,15 +159,13 @@ class App extends StatelessWidget {
pendingDeletionsService: context.read(),
),
),
- BlocProvider(
- create: (context) => OverviewBloc(
- dashboardSummaryRepository: context
- .read>(),
- headlinesRepository: context.read>(),
- topicsRepository: context.read>(),
- sourcesRepository: context.read>(),
- ),
- ),
+ // BlocProvider(
+ // create: (context) => OverviewBloc(
+ // headlinesRepository: context.read>(),
+ // topicsRepository: context.read>(),
+ // sourcesRepository: context.read>(),
+ // ),
+ // ),
// The UserFilterBloc is provided here to be available for both the
// UserManagementBloc and the UI components.
BlocProvider(create: (_) => UserFilterBloc()),
diff --git a/lib/app_configuration/view/tabs/features_configuration_tab.dart b/lib/app_configuration/view/tabs/features_configuration_tab.dart
index f6f68a6d..8453bf96 100644
--- a/lib/app_configuration/view/tabs/features_configuration_tab.dart
+++ b/lib/app_configuration/view/tabs/features_configuration_tab.dart
@@ -2,16 +2,23 @@ import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/ad_config_form.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/ad_platform_config_form.dart';
+import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/analytics_config_form.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/community_config_form.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/feed_ad_settings_form.dart';
-import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/feed_decorator_form.dart';
+import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/feed_config_form.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/navigation_ad_settings_form.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/push_notification_settings_form.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
-import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/feed_decorator_type_l10n.dart';
-import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/feed_item_click_behavior_l10n.dart';
import 'package:ui_kit/ui_kit.dart';
+enum _FeatureTile {
+ advertisements,
+ pushNotifications,
+ analytics,
+ feed,
+ community,
+}
+
/// {@template features_configuration_tab}
/// A widget representing the "Features" tab in the App Configuration page.
///
@@ -41,35 +48,15 @@ class _FeaturesConfigurationTabState extends State {
/// Notifier for the index of the currently expanded top-level ExpansionTile.
///
/// A value of `null` means no tile is expanded.
- final ValueNotifier _expandedTileIndex = ValueNotifier(null);
+ final ValueNotifier<_FeatureTile?> _expandedTile =
+ ValueNotifier<_FeatureTile?>(null);
@override
void dispose() {
- _expandedTileIndex.dispose();
+ _expandedTile.dispose();
super.dispose();
}
- String _getDecoratorDescription(
- BuildContext context,
- FeedDecoratorType type,
- ) {
- final l10n = AppLocalizationsX(context).l10n;
- switch (type) {
- case FeedDecoratorType.linkAccount:
- return l10n.feedDecoratorLinkAccountDescription;
- case FeedDecoratorType.upgrade:
- return l10n.feedDecoratorUpgradeDescription;
- case FeedDecoratorType.rateApp:
- return l10n.feedDecoratorRateAppDescription;
- case FeedDecoratorType.enableNotifications:
- return l10n.feedDecoratorEnableNotificationsDescription;
- case FeedDecoratorType.suggestedTopics:
- return l10n.feedDecoratorSuggestedTopicsDescription;
- case FeedDecoratorType.suggestedSources:
- return l10n.feedDecoratorSuggestedSourcesDescription;
- }
- }
-
@override
Widget build(BuildContext context) {
final l10n = AppLocalizationsX(context).l10n;
@@ -78,10 +65,10 @@ class _FeaturesConfigurationTabState extends State {
padding: const EdgeInsets.all(AppSpacing.lg),
children: [
// Advertisements
- ValueListenableBuilder(
- valueListenable: _expandedTileIndex,
- builder: (context, expandedIndex, child) {
- const tileIndex = 0;
+ ValueListenableBuilder<_FeatureTile?>(
+ valueListenable: _expandedTile,
+ builder: (context, expandedTile, child) {
+ const tile = _FeatureTile.advertisements;
return ExpansionTile(
leading: Icon(
Icons.paid_outlined,
@@ -89,7 +76,7 @@ class _FeaturesConfigurationTabState extends State {
0.7,
),
),
- key: ValueKey('advertisementsTile_$expandedIndex'),
+ key: ValueKey('advertisementsTile_$expandedTile'),
title: Text(l10n.advertisementsTab),
subtitle: Text(
l10n.advertisementsDescription,
@@ -100,9 +87,9 @@ class _FeaturesConfigurationTabState extends State {
),
),
onExpansionChanged: (bool isExpanded) {
- _expandedTileIndex.value = isExpanded ? tileIndex : null;
+ _expandedTile.value = isExpanded ? tile : null;
},
- initiallyExpanded: expandedIndex == tileIndex,
+ initiallyExpanded: expandedTile == tile,
childrenPadding: const EdgeInsetsDirectional.only(
start: AppSpacing.xxl,
top: AppSpacing.md,
@@ -114,21 +101,23 @@ class _FeaturesConfigurationTabState extends State {
remoteConfig: widget.remoteConfig,
onConfigChanged: widget.onConfigChanged,
),
- const SizedBox(height: AppSpacing.lg),
- AdPlatformConfigForm(
- remoteConfig: widget.remoteConfig,
- onConfigChanged: widget.onConfigChanged,
- ),
- const SizedBox(height: AppSpacing.lg),
- FeedAdSettingsForm(
- remoteConfig: widget.remoteConfig,
- onConfigChanged: widget.onConfigChanged,
- ),
- const SizedBox(height: AppSpacing.lg),
- NavigationAdSettingsForm(
- remoteConfig: widget.remoteConfig,
- onConfigChanged: widget.onConfigChanged,
- ),
+ if (widget.remoteConfig.features.ads.enabled) ...[
+ const SizedBox(height: AppSpacing.lg),
+ AdPlatformConfigForm(
+ remoteConfig: widget.remoteConfig,
+ onConfigChanged: widget.onConfigChanged,
+ ),
+ const SizedBox(height: AppSpacing.lg),
+ FeedAdSettingsForm(
+ remoteConfig: widget.remoteConfig,
+ onConfigChanged: widget.onConfigChanged,
+ ),
+ const SizedBox(height: AppSpacing.lg),
+ NavigationAdSettingsForm(
+ remoteConfig: widget.remoteConfig,
+ onConfigChanged: widget.onConfigChanged,
+ ),
+ ],
],
);
},
@@ -136,10 +125,10 @@ class _FeaturesConfigurationTabState extends State {
const SizedBox(height: AppSpacing.lg),
// Push Notifications
- ValueListenableBuilder(
- valueListenable: _expandedTileIndex,
- builder: (context, expandedIndex, child) {
- const tileIndex = 1;
+ ValueListenableBuilder<_FeatureTile?>(
+ valueListenable: _expandedTile,
+ builder: (context, expandedTile, child) {
+ const tile = _FeatureTile.pushNotifications;
return ExpansionTile(
leading: Icon(
Icons.notifications_active_outlined,
@@ -147,7 +136,7 @@ class _FeaturesConfigurationTabState extends State {
0.7,
),
),
- key: ValueKey('pushNotificationsTile_$expandedIndex'),
+ key: ValueKey('pushNotificationsTile_$expandedTile'),
title: Text(l10n.notificationsTab),
subtitle: Text(
l10n.notificationsDescription,
@@ -158,9 +147,9 @@ class _FeaturesConfigurationTabState extends State {
),
),
onExpansionChanged: (bool isExpanded) {
- _expandedTileIndex.value = isExpanded ? tileIndex : null;
+ _expandedTile.value = isExpanded ? tile : null;
},
- initiallyExpanded: expandedIndex == tileIndex,
+ initiallyExpanded: expandedTile == tile,
childrenPadding: const EdgeInsetsDirectional.only(
start: AppSpacing.xxl,
top: AppSpacing.md,
@@ -178,11 +167,54 @@ class _FeaturesConfigurationTabState extends State {
),
const SizedBox(height: AppSpacing.lg),
+ // Analytics
+ ValueListenableBuilder<_FeatureTile?>(
+ valueListenable: _expandedTile,
+ builder: (context, expandedTile, child) {
+ const tile = _FeatureTile.analytics;
+ return ExpansionTile(
+ leading: Icon(
+ Icons.analytics_outlined,
+ color: Theme.of(context).colorScheme.onSurface.withOpacity(
+ 0.7,
+ ),
+ ),
+ key: ValueKey('analyticsTile_$expandedTile'),
+ title: Text(l10n.analyticsTab),
+ subtitle: Text(
+ l10n.analyticsDescription,
+ style: Theme.of(context).textTheme.bodySmall?.copyWith(
+ color: Theme.of(
+ context,
+ ).colorScheme.onSurface.withOpacity(0.7),
+ ),
+ ),
+ onExpansionChanged: (bool isExpanded) {
+ _expandedTile.value = isExpanded ? tile : null;
+ },
+ initiallyExpanded: expandedTile == tile,
+ childrenPadding: const EdgeInsetsDirectional.only(
+ start: AppSpacing.xxl,
+ top: AppSpacing.md,
+ bottom: AppSpacing.md,
+ ),
+ expandedCrossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ AnalyticsConfigForm(
+ remoteConfig: widget.remoteConfig,
+ onConfigChanged: widget.onConfigChanged,
+ ),
+ ],
+ );
+ },
+ ),
+ const SizedBox(height: AppSpacing.lg),
+
// Feed
- ValueListenableBuilder(
- valueListenable: _expandedTileIndex,
- builder: (context, expandedIndex, child) {
- const tileIndex = 2;
+ ValueListenableBuilder<_FeatureTile?>(
+ valueListenable: _expandedTile,
+ builder: (context, expandedTile, child) {
+ const tile = _FeatureTile.feed;
return ExpansionTile(
leading: Icon(
Icons.dynamic_feed_outlined,
@@ -190,7 +222,7 @@ class _FeaturesConfigurationTabState extends State {
0.7,
),
),
- key: ValueKey('feedTile_$expandedIndex'),
+ key: ValueKey('feedTile_$expandedTile'),
title: Text(l10n.feedTab),
subtitle: Text(
l10n.feedDescription,
@@ -201,9 +233,9 @@ class _FeaturesConfigurationTabState extends State {
),
),
onExpansionChanged: (bool isExpanded) {
- _expandedTileIndex.value = isExpanded ? tileIndex : null;
+ _expandedTile.value = isExpanded ? tile : null;
},
- initiallyExpanded: expandedIndex == tileIndex,
+ initiallyExpanded: expandedTile == tile,
childrenPadding: const EdgeInsetsDirectional.only(
start: AppSpacing.xxl,
top: AppSpacing.md,
@@ -211,105 +243,9 @@ class _FeaturesConfigurationTabState extends State {
),
expandedCrossAxisAlignment: CrossAxisAlignment.start,
children: [
- ExpansionTile(
- title: Text(l10n.feedItemClickBehaviorTitle),
- subtitle: Text(
- l10n.feedItemClickBehaviorDescription,
- style: Theme.of(context).textTheme.bodySmall?.copyWith(
- color: Theme.of(
- context,
- ).colorScheme.onSurface.withOpacity(0.7),
- ),
- ),
- childrenPadding: const EdgeInsetsDirectional.only(
- start: AppSpacing.lg,
- top: AppSpacing.md,
- bottom: AppSpacing.md,
- ),
- expandedCrossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Align(
- alignment: AlignmentDirectional.centerStart,
- child: SegmentedButton(
- segments: FeedItemClickBehavior.values
- .where(
- (b) => b != FeedItemClickBehavior.defaultBehavior,
- )
- .map(
- (behavior) =>
- ButtonSegment(
- value: behavior,
- label: Text(behavior.l10n(context)),
- ),
- )
- .toList(),
- selected: {
- widget.remoteConfig.features.feed.itemClickBehavior,
- },
- onSelectionChanged: (newSelection) {
- widget.onConfigChanged(
- widget.remoteConfig.copyWith(
- features: widget.remoteConfig.features.copyWith(
- feed: widget.remoteConfig.features.feed
- .copyWith(
- itemClickBehavior: newSelection.first,
- ),
- ),
- ),
- );
- },
- ),
- ),
- ],
- ),
- const SizedBox(height: AppSpacing.lg),
- ExpansionTile(
- title: Text(l10n.feedDecoratorsTitle),
- subtitle: Text(
- l10n.feedDecoratorsDescription,
- style: Theme.of(context).textTheme.bodySmall?.copyWith(
- color: Theme.of(
- context,
- ).colorScheme.onSurface.withOpacity(0.7),
- ),
- ),
- childrenPadding: const EdgeInsetsDirectional.only(
- start: AppSpacing.lg,
- top: AppSpacing.md,
- bottom: AppSpacing.md,
- ),
- expandedCrossAxisAlignment: CrossAxisAlignment.start,
- children: [
- for (final decoratorType in FeedDecoratorType.values)
- Padding(
- padding: const EdgeInsets.only(bottom: AppSpacing.md),
- child: ExpansionTile(
- title: Text(decoratorType.l10n(context)),
- subtitle: Text(
- _getDecoratorDescription(context, decoratorType),
- style: Theme.of(context).textTheme.bodySmall
- ?.copyWith(
- color: Theme.of(
- context,
- ).colorScheme.onSurface.withOpacity(0.7),
- ),
- ),
- childrenPadding: const EdgeInsetsDirectional.only(
- start: AppSpacing.xl,
- top: AppSpacing.md,
- bottom: AppSpacing.md,
- ),
- expandedCrossAxisAlignment: CrossAxisAlignment.start,
- children: [
- FeedDecoratorForm(
- decoratorType: decoratorType,
- remoteConfig: widget.remoteConfig,
- onConfigChanged: widget.onConfigChanged,
- ),
- ],
- ),
- ),
- ],
+ FeedConfigForm(
+ remoteConfig: widget.remoteConfig,
+ onConfigChanged: widget.onConfigChanged,
),
],
);
@@ -318,10 +254,10 @@ class _FeaturesConfigurationTabState extends State {
const SizedBox(height: AppSpacing.lg),
// Community & Engagement
- ValueListenableBuilder(
- valueListenable: _expandedTileIndex,
- builder: (context, expandedIndex, child) {
- const tileIndex = 3;
+ ValueListenableBuilder<_FeatureTile?>(
+ valueListenable: _expandedTile,
+ builder: (context, expandedTile, child) {
+ const tile = _FeatureTile.community;
return ExpansionTile(
leading: Icon(
Icons.groups_outlined,
@@ -329,7 +265,7 @@ class _FeaturesConfigurationTabState extends State {
0.7,
),
),
- key: ValueKey('communityTile_$expandedIndex'),
+ key: ValueKey('communityTile_$expandedTile'),
title: Text(l10n.communityAndEngagementTitle),
subtitle: Text(
l10n.communityAndEngagementDescription,
@@ -340,9 +276,9 @@ class _FeaturesConfigurationTabState extends State {
),
),
onExpansionChanged: (bool isExpanded) {
- _expandedTileIndex.value = isExpanded ? tileIndex : null;
+ _expandedTile.value = isExpanded ? tile : null;
},
- initiallyExpanded: expandedIndex == tileIndex,
+ initiallyExpanded: expandedTile == tile,
childrenPadding: const EdgeInsetsDirectional.only(
start: AppSpacing.xxl,
top: AppSpacing.md,
diff --git a/lib/app_configuration/widgets/analytics_config_form.dart b/lib/app_configuration/widgets/analytics_config_form.dart
new file mode 100644
index 00000000..e4cfea02
--- /dev/null
+++ b/lib/app_configuration/widgets/analytics_config_form.dart
@@ -0,0 +1,426 @@
+import 'package:core/core.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
+import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
+import 'package:ui_kit/ui_kit.dart';
+
+/// {@template analytics_config_form}
+/// A form widget for configuring analytics settings.
+/// {@endtemplate}
+class AnalyticsConfigForm extends StatelessWidget {
+ /// {@macro analytics_config_form}
+ const AnalyticsConfigForm({
+ required this.remoteConfig,
+ required this.onConfigChanged,
+ super.key,
+ });
+
+ /// The current [RemoteConfig] object.
+ final RemoteConfig remoteConfig;
+
+ /// Callback to notify parent of changes to the [RemoteConfig].
+ final ValueChanged onConfigChanged;
+
+ @override
+ Widget build(BuildContext context) {
+ final l10n = AppLocalizationsX(context).l10n;
+ final features = remoteConfig.features;
+ final analyticsConfig = features.analytics;
+
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SwitchListTile(
+ title: Text(l10n.analyticsSystemStatusTitle),
+ subtitle: Text(l10n.analyticsSystemStatusDescription),
+ value: analyticsConfig.enabled,
+ onChanged: (value) {
+ onConfigChanged(
+ remoteConfig.copyWith(
+ features: features.copyWith(
+ analytics: analyticsConfig.copyWith(enabled: value),
+ ),
+ ),
+ );
+ },
+ ),
+ if (analyticsConfig.enabled) ...[
+ const SizedBox(height: AppSpacing.lg),
+ _buildProviderSection(context, l10n, analyticsConfig),
+ const SizedBox(height: AppSpacing.lg),
+ _buildEventsSection(context, l10n, analyticsConfig),
+ ],
+ ],
+ );
+ }
+
+ Widget _buildProviderSection(
+ BuildContext context,
+ AppLocalizations l10n,
+ AnalyticsConfig config,
+ ) {
+ return ExpansionTile(
+ title: Text(l10n.analyticsProviderTitle),
+ subtitle: Text(
+ l10n.analyticsProviderDescription,
+ style: Theme.of(context).textTheme.bodySmall?.copyWith(
+ color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
+ ),
+ ),
+ childrenPadding: const EdgeInsetsDirectional.only(
+ start: AppSpacing.lg,
+ top: AppSpacing.md,
+ bottom: AppSpacing.md,
+ ),
+ expandedCrossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Align(
+ alignment: AlignmentDirectional.centerStart,
+ child: SegmentedButton(
+ segments: AnalyticsProvider.values
+ .where((provider) => provider != AnalyticsProvider.demo)
+ .map((provider) {
+ return ButtonSegment(
+ value: provider,
+ label: Text(_getProviderLabel(l10n, provider)),
+ );
+ })
+ .toList(),
+ selected: {config.activeProvider},
+ onSelectionChanged: (newSelection) {
+ onConfigChanged(
+ remoteConfig.copyWith(
+ features: remoteConfig.features.copyWith(
+ analytics: config.copyWith(
+ activeProvider: newSelection.first,
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _buildEventsSection(
+ BuildContext context,
+ AppLocalizations l10n,
+ AnalyticsConfig config,
+ ) {
+ return ExpansionTile(
+ title: Text(l10n.analyticsEventsTitle),
+ subtitle: Text(
+ l10n.analyticsEventsDescription,
+ style: Theme.of(context).textTheme.bodySmall?.copyWith(
+ color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
+ ),
+ ),
+ childrenPadding: const EdgeInsetsDirectional.only(
+ start: AppSpacing.lg,
+ top: AppSpacing.md,
+ bottom: AppSpacing.md,
+ ),
+ expandedCrossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // We use a Column here instead of ListView because it's inside a scrollable parent
+ Column(
+ children: AnalyticsEvent.values.map((event) {
+ final isEnabled = !config.disabledEvents.contains(event);
+ final samplingRate = config.eventSamplingRates[event] ?? 1.0;
+ final (label, description) = _getEventInfo(l10n, event);
+
+ return Column(
+ children: [
+ CheckboxListTile(
+ title: Text(label),
+ subtitle: Text(
+ description,
+ style: Theme.of(context).textTheme.bodySmall,
+ ),
+ value: isEnabled,
+ onChanged: (value) {
+ final newDisabledEvents = Set.from(
+ config.disabledEvents,
+ );
+ if (value ?? false) {
+ newDisabledEvents.remove(event);
+ } else {
+ newDisabledEvents.add(event);
+ }
+ onConfigChanged(
+ remoteConfig.copyWith(
+ features: remoteConfig.features.copyWith(
+ analytics: config.copyWith(
+ disabledEvents: newDisabledEvents,
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ if (isEnabled)
+ Padding(
+ padding: const EdgeInsetsDirectional.only(
+ start: AppSpacing.xxl,
+ end: AppSpacing.md,
+ bottom: AppSpacing.sm,
+ ),
+ child: Row(
+ children: [
+ Text(
+ l10n.samplingRateLabel(
+ (samplingRate * 100).toInt(),
+ ),
+ style: Theme.of(context).textTheme.bodySmall,
+ ),
+ Expanded(
+ child: Slider(
+ value: samplingRate,
+ min: 0,
+ max: 1,
+ divisions: 20,
+ label: '${(samplingRate * 100).toInt()}%',
+ onChanged: (value) {
+ final newSamplingRates =
+ Map.from(
+ config.eventSamplingRates,
+ );
+ // If value is 1.0, we can remove it from the map to save space/default
+ if (value == 1.0) {
+ newSamplingRates.remove(event);
+ } else {
+ newSamplingRates[event] = value;
+ }
+ onConfigChanged(
+ remoteConfig.copyWith(
+ features: remoteConfig.features.copyWith(
+ analytics: config.copyWith(
+ eventSamplingRates: newSamplingRates,
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ const Divider(height: 1),
+ ],
+ );
+ }).toList(),
+ ),
+ ],
+ );
+ }
+
+ String _getProviderLabel(AppLocalizations l10n, AnalyticsProvider provider) {
+ switch (provider) {
+ case AnalyticsProvider.firebase:
+ return l10n.analyticsProviderFirebase;
+ case AnalyticsProvider.mixpanel:
+ return l10n.analyticsProviderMixpanel;
+ case AnalyticsProvider.demo:
+ // Fallback, though filtered out in UI
+ return 'Demo';
+ }
+ }
+
+ (String, String) _getEventInfo(AppLocalizations l10n, AnalyticsEvent event) {
+ switch (event) {
+ case AnalyticsEvent.userRegistered:
+ return (
+ l10n.analyticsEventUserRegisteredLabel,
+ l10n.analyticsEventUserRegisteredDescription,
+ );
+ case AnalyticsEvent.userLogin:
+ return (
+ l10n.analyticsEventUserLoginLabel,
+ l10n.analyticsEventUserLoginDescription,
+ );
+ case AnalyticsEvent.accountLinked:
+ return (
+ l10n.analyticsEventAccountLinkedLabel,
+ l10n.analyticsEventAccountLinkedDescription,
+ );
+ case AnalyticsEvent.userRoleChanged:
+ return (
+ l10n.analyticsEventUserRoleChangedLabel,
+ l10n.analyticsEventUserRoleChangedDescription,
+ );
+ case AnalyticsEvent.contentViewed:
+ return (
+ l10n.analyticsEventContentViewedLabel,
+ l10n.analyticsEventContentViewedDescription,
+ );
+ case AnalyticsEvent.contentShared:
+ return (
+ l10n.analyticsEventContentSharedLabel,
+ l10n.analyticsEventContentSharedDescription,
+ );
+ case AnalyticsEvent.contentSaved:
+ return (
+ l10n.analyticsEventContentSavedLabel,
+ l10n.analyticsEventContentSavedDescription,
+ );
+ case AnalyticsEvent.contentUnsaved:
+ return (
+ l10n.analyticsEventContentUnsavedLabel,
+ l10n.analyticsEventContentUnsavedDescription,
+ );
+ case AnalyticsEvent.contentReadingTime:
+ return (
+ l10n.analyticsEventContentReadingTimeLabel,
+ l10n.analyticsEventContentReadingTimeDescription,
+ );
+ case AnalyticsEvent.reactionCreated:
+ return (
+ l10n.analyticsEventReactionCreatedLabel,
+ l10n.analyticsEventReactionCreatedDescription,
+ );
+ case AnalyticsEvent.reactionDeleted:
+ return (
+ l10n.analyticsEventReactionDeletedLabel,
+ l10n.analyticsEventReactionDeletedDescription,
+ );
+ case AnalyticsEvent.commentCreated:
+ return (
+ l10n.analyticsEventCommentCreatedLabel,
+ l10n.analyticsEventCommentCreatedDescription,
+ );
+ case AnalyticsEvent.commentDeleted:
+ return (
+ l10n.analyticsEventCommentDeletedLabel,
+ l10n.analyticsEventCommentDeletedDescription,
+ );
+ case AnalyticsEvent.reportSubmitted:
+ return (
+ l10n.analyticsEventReportSubmittedLabel,
+ l10n.analyticsEventReportSubmittedDescription,
+ );
+ case AnalyticsEvent.headlineFilterCreated:
+ return (
+ l10n.analyticsEventHeadlineFilterCreatedLabel,
+ l10n.analyticsEventHeadlineFilterCreatedDescription,
+ );
+ case AnalyticsEvent.headlineFilterUpdated:
+ return (
+ l10n.analyticsEventHeadlineFilterUpdatedLabel,
+ l10n.analyticsEventHeadlineFilterUpdatedDescription,
+ );
+ case AnalyticsEvent.headlineFilterUsed:
+ return (
+ l10n.analyticsEventHeadlineFilterUsedLabel,
+ l10n.analyticsEventHeadlineFilterUsedDescription,
+ );
+ case AnalyticsEvent.sourceFilterCreated:
+ return (
+ l10n.analyticsEventSourceFilterCreatedLabel,
+ l10n.analyticsEventSourceFilterCreatedDescription,
+ );
+ case AnalyticsEvent.sourceFilterUpdated:
+ return (
+ l10n.analyticsEventSourceFilterUpdatedLabel,
+ l10n.analyticsEventSourceFilterUpdatedDescription,
+ );
+ case AnalyticsEvent.searchPerformed:
+ return (
+ l10n.analyticsEventSearchPerformedLabel,
+ l10n.analyticsEventSearchPerformedDescription,
+ );
+ case AnalyticsEvent.appReviewPromptResponded:
+ return (
+ l10n.analyticsEventAppReviewPromptRespondedLabel,
+ l10n.analyticsEventAppReviewPromptRespondedDescription,
+ );
+ case AnalyticsEvent.appReviewStoreRequested:
+ return (
+ l10n.analyticsEventAppReviewStoreRequestedLabel,
+ l10n.analyticsEventAppReviewStoreRequestedDescription,
+ );
+ case AnalyticsEvent.limitExceeded:
+ return (
+ l10n.analyticsEventLimitExceededLabel,
+ l10n.analyticsEventLimitExceededDescription,
+ );
+ case AnalyticsEvent.limitExceededCtaClicked:
+ return (
+ l10n.analyticsEventLimitExceededCtaClickedLabel,
+ l10n.analyticsEventLimitExceededCtaClickedDescription,
+ );
+ case AnalyticsEvent.paywallPresented:
+ return (
+ l10n.analyticsEventPaywallPresentedLabel,
+ l10n.analyticsEventPaywallPresentedDescription,
+ );
+ case AnalyticsEvent.subscriptionStarted:
+ return (
+ l10n.analyticsEventSubscriptionStartedLabel,
+ l10n.analyticsEventSubscriptionStartedDescription,
+ );
+ case AnalyticsEvent.subscriptionRenewed:
+ return (
+ l10n.analyticsEventSubscriptionRenewedLabel,
+ l10n.analyticsEventSubscriptionRenewedDescription,
+ );
+ case AnalyticsEvent.subscriptionCancelled:
+ return (
+ l10n.analyticsEventSubscriptionCancelledLabel,
+ l10n.analyticsEventSubscriptionCancelledDescription,
+ );
+ case AnalyticsEvent.subscriptionEnded:
+ return (
+ l10n.analyticsEventSubscriptionEndedLabel,
+ l10n.analyticsEventSubscriptionEndedDescription,
+ );
+ case AnalyticsEvent.adImpression:
+ return (
+ l10n.analyticsEventAdImpressionLabel,
+ l10n.analyticsEventAdImpressionDescription,
+ );
+ case AnalyticsEvent.adClicked:
+ return (
+ l10n.analyticsEventAdClickedLabel,
+ l10n.analyticsEventAdClickedDescription,
+ );
+ case AnalyticsEvent.adLoadFailed:
+ return (
+ l10n.analyticsEventAdLoadFailedLabel,
+ l10n.analyticsEventAdLoadFailedDescription,
+ );
+ case AnalyticsEvent.adRewardEarned:
+ return (
+ l10n.analyticsEventAdRewardEarnedLabel,
+ l10n.analyticsEventAdRewardEarnedDescription,
+ );
+ case AnalyticsEvent.themeChanged:
+ return (
+ l10n.analyticsEventThemeChangedLabel,
+ l10n.analyticsEventThemeChangedDescription,
+ );
+ case AnalyticsEvent.languageChanged:
+ return (
+ l10n.analyticsEventLanguageChangedLabel,
+ l10n.analyticsEventLanguageChangedDescription,
+ );
+ case AnalyticsEvent.feedDensityChanged:
+ return (
+ l10n.analyticsEventFeedDensityChangedLabel,
+ l10n.analyticsEventFeedDensityChangedDescription,
+ );
+ case AnalyticsEvent.browserChoiceChanged:
+ return (
+ l10n.analyticsEventBrowserChoiceChangedLabel,
+ l10n.analyticsEventBrowserChoiceChangedDescription,
+ );
+ case AnalyticsEvent.sourceFilterUsed:
+ return (
+ l10n.analyticsEventSourceFilterUsedLabel,
+ l10n.analyticsEventSourceFilterUsedDescription,
+ );
+ }
+ }
+}
diff --git a/lib/app_configuration/widgets/feed_config_form.dart b/lib/app_configuration/widgets/feed_config_form.dart
new file mode 100644
index 00000000..5133278a
--- /dev/null
+++ b/lib/app_configuration/widgets/feed_config_form.dart
@@ -0,0 +1,153 @@
+import 'package:core/core.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/feed_decorator_form.dart';
+import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
+import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/feed_decorator_type_l10n.dart';
+import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/feed_item_click_behavior_l10n.dart';
+import 'package:ui_kit/ui_kit.dart';
+
+/// {@template feed_config_form}
+/// A form widget for configuring feed settings.
+/// {@endtemplate}
+class FeedConfigForm extends StatelessWidget {
+ /// {@macro feed_config_form}
+ const FeedConfigForm({
+ required this.remoteConfig,
+ required this.onConfigChanged,
+ super.key,
+ });
+
+ /// The current [RemoteConfig] object.
+ final RemoteConfig remoteConfig;
+
+ /// Callback to notify parent of changes to the [RemoteConfig].
+ final ValueChanged onConfigChanged;
+
+ String _getDecoratorDescription(
+ BuildContext context,
+ FeedDecoratorType type,
+ ) {
+ final l10n = AppLocalizationsX(context).l10n;
+ switch (type) {
+ case FeedDecoratorType.linkAccount:
+ return l10n.feedDecoratorLinkAccountDescription;
+ case FeedDecoratorType.upgrade:
+ return l10n.feedDecoratorUpgradeDescription;
+ case FeedDecoratorType.rateApp:
+ return l10n.feedDecoratorRateAppDescription;
+ case FeedDecoratorType.enableNotifications:
+ return l10n.feedDecoratorEnableNotificationsDescription;
+ case FeedDecoratorType.suggestedTopics:
+ return l10n.feedDecoratorSuggestedTopicsDescription;
+ case FeedDecoratorType.suggestedSources:
+ return l10n.feedDecoratorSuggestedSourcesDescription;
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final l10n = AppLocalizationsX(context).l10n;
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ExpansionTile(
+ title: Text(l10n.feedItemClickBehaviorTitle),
+ subtitle: Text(
+ l10n.feedItemClickBehaviorDescription,
+ style: Theme.of(context).textTheme.bodySmall?.copyWith(
+ color: Theme.of(
+ context,
+ ).colorScheme.onSurface.withOpacity(0.7),
+ ),
+ ),
+ childrenPadding: const EdgeInsetsDirectional.only(
+ start: AppSpacing.lg,
+ top: AppSpacing.md,
+ bottom: AppSpacing.md,
+ ),
+ expandedCrossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Align(
+ alignment: AlignmentDirectional.centerStart,
+ child: SegmentedButton(
+ segments: FeedItemClickBehavior.values
+ .where(
+ (b) => b != FeedItemClickBehavior.defaultBehavior,
+ )
+ .map(
+ (behavior) => ButtonSegment(
+ value: behavior,
+ label: Text(behavior.l10n(context)),
+ ),
+ )
+ .toList(),
+ selected: {
+ remoteConfig.features.feed.itemClickBehavior,
+ },
+ onSelectionChanged: (newSelection) {
+ onConfigChanged(
+ remoteConfig.copyWith(
+ features: remoteConfig.features.copyWith(
+ feed: remoteConfig.features.feed.copyWith(
+ itemClickBehavior: newSelection.first,
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: AppSpacing.lg),
+ ExpansionTile(
+ title: Text(l10n.feedDecoratorsTitle),
+ subtitle: Text(
+ l10n.feedDecoratorsDescription,
+ style: Theme.of(context).textTheme.bodySmall?.copyWith(
+ color: Theme.of(
+ context,
+ ).colorScheme.onSurface.withOpacity(0.7),
+ ),
+ ),
+ childrenPadding: const EdgeInsetsDirectional.only(
+ start: AppSpacing.lg,
+ top: AppSpacing.md,
+ bottom: AppSpacing.md,
+ ),
+ expandedCrossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ for (final decoratorType in FeedDecoratorType.values)
+ Padding(
+ padding: const EdgeInsets.only(bottom: AppSpacing.md),
+ child: ExpansionTile(
+ title: Text(decoratorType.l10n(context)),
+ subtitle: Text(
+ _getDecoratorDescription(context, decoratorType),
+ style: Theme.of(context).textTheme.bodySmall?.copyWith(
+ color: Theme.of(
+ context,
+ ).colorScheme.onSurface.withOpacity(0.7),
+ ),
+ ),
+ childrenPadding: const EdgeInsetsDirectional.only(
+ start: AppSpacing.xl,
+ top: AppSpacing.md,
+ bottom: AppSpacing.md,
+ ),
+ expandedCrossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ FeedDecoratorForm(
+ decoratorType: decoratorType,
+ remoteConfig: remoteConfig,
+ onConfigChanged: onConfigChanged,
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/app_configuration/widgets/push_notification_settings_form.dart b/lib/app_configuration/widgets/push_notification_settings_form.dart
index c35089ac..b3d6eb70 100644
--- a/lib/app_configuration/widgets/push_notification_settings_form.dart
+++ b/lib/app_configuration/widgets/push_notification_settings_form.dart
@@ -46,10 +46,12 @@ class PushNotificationSettingsForm extends StatelessWidget {
);
},
),
- const SizedBox(height: AppSpacing.lg),
- _buildPrimaryProviderSection(context, l10n, pushConfig),
- const SizedBox(height: AppSpacing.lg),
- _buildDeliveryTypesSection(context, l10n, pushConfig),
+ if (pushConfig.enabled) ...[
+ const SizedBox(height: AppSpacing.lg),
+ _buildPrimaryProviderSection(context, l10n, pushConfig),
+ const SizedBox(height: AppSpacing.lg),
+ _buildDeliveryTypesSection(context, l10n, pushConfig),
+ ],
],
);
}
diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart
index f2b1507a..95a3381e 100644
--- a/lib/bootstrap.dart
+++ b/lib/bootstrap.dart
@@ -61,7 +61,6 @@ Future bootstrap(
DataClient userContentPreferencesClient;
DataClient appSettingsClient;
DataClient remoteConfigClient;
- DataClient dashboardSummaryClient;
DataClient countriesClient;
DataClient languagesClient;
DataClient usersClient;
@@ -106,12 +105,6 @@ Future bootstrap(
initialData: remoteConfigsFixturesData,
logger: Logger('DataInMemory'),
);
- dashboardSummaryClient = DataInMemory(
- toJson: (i) => i.toJson(),
- getId: (i) => i.id,
- initialData: dashboardSummaryFixturesData,
- logger: Logger('DataInMemory'),
- );
countriesClient = DataInMemory(
toJson: (i) => i.toJson(),
getId: (i) => i.id,
@@ -192,13 +185,6 @@ Future bootstrap(
toJson: (config) => config.toJson(),
logger: Logger('DataApi'),
);
- dashboardSummaryClient = DataApi(
- httpClient: httpClient,
- modelName: 'dashboard_summary',
- fromJson: DashboardSummary.fromJson,
- toJson: (summary) => summary.toJson(),
- logger: Logger('DataApi'),
- );
countriesClient = DataApi(
httpClient: httpClient,
modelName: 'country',
@@ -263,9 +249,6 @@ Future bootstrap(
final remoteConfigRepository = DataRepository(
dataClient: remoteConfigClient,
);
- final dashboardSummaryRepository = DataRepository(
- dataClient: dashboardSummaryClient,
- );
final countriesRepository = DataRepository(
dataClient: countriesClient,
);
@@ -291,7 +274,6 @@ Future bootstrap(
appSettingsRepository: appSettingsRepository,
userContentPreferencesRepository: userContentPreferencesRepository,
remoteConfigRepository: remoteConfigRepository,
- dashboardSummaryRepository: dashboardSummaryRepository,
countriesRepository: countriesRepository,
languagesRepository: languagesRepository,
usersRepository: usersRepository,
diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart
index dd4abc09..e387b2a9 100644
--- a/lib/l10n/app_localizations.dart
+++ b/lib/l10n/app_localizations.dart
@@ -4327,6 +4327,528 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Configure what happens after a user responds to the enjoyment prompt, such as requesting a store review.'**
String get followUpActionsDescription;
+
+ /// Tab title for Analytics settings
+ ///
+ /// In en, this message translates to:
+ /// **'Analytics'**
+ String get analyticsTab;
+
+ /// Description for the Analytics expansion tile
+ ///
+ /// In en, this message translates to:
+ /// **'Configure analytics provider, event logging, and sampling rates.'**
+ String get analyticsDescription;
+
+ /// Title for the Analytics System status section
+ ///
+ /// In en, this message translates to:
+ /// **'Enable Analytics System'**
+ String get analyticsSystemStatusTitle;
+
+ /// Description for the Analytics System status section
+ ///
+ /// In en, this message translates to:
+ /// **'Master switch to enable or disable all analytics tracking.'**
+ String get analyticsSystemStatusDescription;
+
+ /// Title for the Analytics Provider section
+ ///
+ /// In en, this message translates to:
+ /// **'Active Provider'**
+ String get analyticsProviderTitle;
+
+ /// Description for the Analytics Provider section
+ ///
+ /// In en, this message translates to:
+ /// **'Select the primary analytics service provider.'**
+ String get analyticsProviderDescription;
+
+ /// Title for the Event Configuration section
+ ///
+ /// In en, this message translates to:
+ /// **'Event Configuration'**
+ String get analyticsEventsTitle;
+
+ /// Description for the Event Configuration section
+ ///
+ /// In en, this message translates to:
+ /// **'Fine-tune logging for specific events. Disable noisy events or adjust sampling rates.'**
+ String get analyticsEventsDescription;
+
+ /// Label for the sampling rate slider
+ ///
+ /// In en, this message translates to:
+ /// **'Sampling Rate: {rate}%'**
+ String samplingRateLabel(int rate);
+
+ /// Label for Firebase analytics provider
+ ///
+ /// In en, this message translates to:
+ /// **'Firebase'**
+ String get analyticsProviderFirebase;
+
+ /// Label for Mixpanel analytics provider
+ ///
+ /// In en, this message translates to:
+ /// **'Mixpanel'**
+ String get analyticsProviderMixpanel;
+
+ /// Label for the User Registration analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'User Registration'**
+ String get analyticsEventUserRegisteredLabel;
+
+ /// Description for the User Registration analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a new user successfully creates an account.'**
+ String get analyticsEventUserRegisteredDescription;
+
+ /// Label for the User Login analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'User Login'**
+ String get analyticsEventUserLoginLabel;
+
+ /// Description for the User Login analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user logs in to the application.'**
+ String get analyticsEventUserLoginDescription;
+
+ /// Label for the Account Linking analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Account Linking'**
+ String get analyticsEventAccountLinkedLabel;
+
+ /// Description for the Account Linking analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a guest user links their account to a permanent credential.'**
+ String get analyticsEventAccountLinkedDescription;
+
+ /// Label for the User Role Change analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'User Role Change'**
+ String get analyticsEventUserRoleChangedLabel;
+
+ /// Description for the User Role Change analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user\'s role is updated (e.g., upgraded to Premium).'**
+ String get analyticsEventUserRoleChangedDescription;
+
+ /// Label for the Content View analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Content View'**
+ String get analyticsEventContentViewedLabel;
+
+ /// Description for the Content View analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user views a headline or article.'**
+ String get analyticsEventContentViewedDescription;
+
+ /// Label for the Content Share analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Content Share'**
+ String get analyticsEventContentSharedLabel;
+
+ /// Description for the Content Share analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user shares content via external platforms.'**
+ String get analyticsEventContentSharedDescription;
+
+ /// Label for the Content Save analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Content Save'**
+ String get analyticsEventContentSavedLabel;
+
+ /// Description for the Content Save analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user bookmarks a headline.'**
+ String get analyticsEventContentSavedDescription;
+
+ /// Label for the Content Unsave analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Content Unsave'**
+ String get analyticsEventContentUnsavedLabel;
+
+ /// Description for the Content Unsave analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user removes a bookmark.'**
+ String get analyticsEventContentUnsavedDescription;
+
+ /// Label for the Content Reading Time analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Content Reading Time'**
+ String get analyticsEventContentReadingTimeLabel;
+
+ /// Description for the Content Reading Time analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track the duration a user spends reading an article.'**
+ String get analyticsEventContentReadingTimeDescription;
+
+ /// Label for the Reaction Added analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Reaction Added'**
+ String get analyticsEventReactionCreatedLabel;
+
+ /// Description for the Reaction Added analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user reacts to content.'**
+ String get analyticsEventReactionCreatedDescription;
+
+ /// Label for the Reaction Removed analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Reaction Removed'**
+ String get analyticsEventReactionDeletedLabel;
+
+ /// Description for the Reaction Removed analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user removes their reaction.'**
+ String get analyticsEventReactionDeletedDescription;
+
+ /// Label for the Comment Posted analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Comment Posted'**
+ String get analyticsEventCommentCreatedLabel;
+
+ /// Description for the Comment Posted analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user submits a new comment.'**
+ String get analyticsEventCommentCreatedDescription;
+
+ /// Label for the Comment Deleted analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Comment Deleted'**
+ String get analyticsEventCommentDeletedLabel;
+
+ /// Description for the Comment Deleted analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user deletes their own comment.'**
+ String get analyticsEventCommentDeletedDescription;
+
+ /// Label for the Report Submitted analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Report Submitted'**
+ String get analyticsEventReportSubmittedLabel;
+
+ /// Description for the Report Submitted analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user reports content or other users.'**
+ String get analyticsEventReportSubmittedDescription;
+
+ /// Label for the Headline Filter Creation analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Headline Filter Creation'**
+ String get analyticsEventHeadlineFilterCreatedLabel;
+
+ /// Description for the Headline Filter Creation analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user creates a new custom headline filter.'**
+ String get analyticsEventHeadlineFilterCreatedDescription;
+
+ /// Label for the Headline Filter Update analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Headline Filter Update'**
+ String get analyticsEventHeadlineFilterUpdatedLabel;
+
+ /// Description for the Headline Filter Update analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user modifies an existing headline filter.'**
+ String get analyticsEventHeadlineFilterUpdatedDescription;
+
+ /// Label for the Headline Filter Usage analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Headline Filter Usage'**
+ String get analyticsEventHeadlineFilterUsedLabel;
+
+ /// Description for the Headline Filter Usage analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user applies a saved headline filter.'**
+ String get analyticsEventHeadlineFilterUsedDescription;
+
+ /// Label for the Source Filter Creation analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Source Filter Creation'**
+ String get analyticsEventSourceFilterCreatedLabel;
+
+ /// Description for the Source Filter Creation analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user creates a new source filter.'**
+ String get analyticsEventSourceFilterCreatedDescription;
+
+ /// Label for the Source Filter Update analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Source Filter Update'**
+ String get analyticsEventSourceFilterUpdatedLabel;
+
+ /// Description for the Source Filter Update analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user modifies an existing source filter.'**
+ String get analyticsEventSourceFilterUpdatedDescription;
+
+ /// Label for the Search Performed analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Search Performed'**
+ String get analyticsEventSearchPerformedLabel;
+
+ /// Description for the Search Performed analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user performs a search query.'**
+ String get analyticsEventSearchPerformedDescription;
+
+ /// Label for the Review Prompt Response analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Review Prompt Response'**
+ String get analyticsEventAppReviewPromptRespondedLabel;
+
+ /// Description for the Review Prompt Response analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track user responses to the internal \'Enjoying the app?\' prompt.'**
+ String get analyticsEventAppReviewPromptRespondedDescription;
+
+ /// Label for the Store Review Request analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Store Review Request'**
+ String get analyticsEventAppReviewStoreRequestedLabel;
+
+ /// Description for the Store Review Request analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when the native OS store review dialog is requested.'**
+ String get analyticsEventAppReviewStoreRequestedDescription;
+
+ /// Label for the Limit Exceeded analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Limit Exceeded'**
+ String get analyticsEventLimitExceededLabel;
+
+ /// Description for the Limit Exceeded analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user hits a usage limit (e.g., saved items limit).'**
+ String get analyticsEventLimitExceededDescription;
+
+ /// Label for the Limit CTA Click analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Limit CTA Click'**
+ String get analyticsEventLimitExceededCtaClickedLabel;
+
+ /// Description for the Limit CTA Click analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track clicks on \'Upgrade\' or \'Link Account\' buttons in limit dialogs.'**
+ String get analyticsEventLimitExceededCtaClickedDescription;
+
+ /// Label for the Paywall Impression analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Paywall Impression'**
+ String get analyticsEventPaywallPresentedLabel;
+
+ /// Description for the Paywall Impression analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when the paywall screen is shown to a user.'**
+ String get analyticsEventPaywallPresentedDescription;
+
+ /// Label for the Subscription Start analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Subscription Start'**
+ String get analyticsEventSubscriptionStartedLabel;
+
+ /// Description for the Subscription Start analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user successfully starts a new subscription.'**
+ String get analyticsEventSubscriptionStartedDescription;
+
+ /// Label for the Subscription Renewal analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Subscription Renewal'**
+ String get analyticsEventSubscriptionRenewedLabel;
+
+ /// Description for the Subscription Renewal analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a subscription is automatically or manually renewed.'**
+ String get analyticsEventSubscriptionRenewedDescription;
+
+ /// Label for the Subscription Cancellation analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Subscription Cancellation'**
+ String get analyticsEventSubscriptionCancelledLabel;
+
+ /// Description for the Subscription Cancellation analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user cancels their subscription.'**
+ String get analyticsEventSubscriptionCancelledDescription;
+
+ /// Label for the Subscription End analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Subscription End'**
+ String get analyticsEventSubscriptionEndedLabel;
+
+ /// Description for the Subscription End analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a subscription expires or is terminated.'**
+ String get analyticsEventSubscriptionEndedDescription;
+
+ /// Label for the Ad Impression analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Ad Impression'**
+ String get analyticsEventAdImpressionLabel;
+
+ /// Description for the Ad Impression analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when an advertisement is displayed to the user.'**
+ String get analyticsEventAdImpressionDescription;
+
+ /// Label for the Ad Click analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Ad Click'**
+ String get analyticsEventAdClickedLabel;
+
+ /// Description for the Ad Click analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user clicks on an advertisement.'**
+ String get analyticsEventAdClickedDescription;
+
+ /// Label for the Ad Load Failure analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Ad Load Failure'**
+ String get analyticsEventAdLoadFailedLabel;
+
+ /// Description for the Ad Load Failure analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track errors when attempting to load advertisements.'**
+ String get analyticsEventAdLoadFailedDescription;
+
+ /// Label for the Ad Reward Earned analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Ad Reward Earned'**
+ String get analyticsEventAdRewardEarnedLabel;
+
+ /// Description for the Ad Reward Earned analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user completes a rewarded ad action.'**
+ String get analyticsEventAdRewardEarnedDescription;
+
+ /// Label for the Theme Change analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Theme Change'**
+ String get analyticsEventThemeChangedLabel;
+
+ /// Description for the Theme Change analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user changes the application theme.'**
+ String get analyticsEventThemeChangedDescription;
+
+ /// Label for the Language Change analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Language Change'**
+ String get analyticsEventLanguageChangedLabel;
+
+ /// Description for the Language Change analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user changes the application language.'**
+ String get analyticsEventLanguageChangedDescription;
+
+ /// Label for the Feed Density Change analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Feed Density Change'**
+ String get analyticsEventFeedDensityChangedLabel;
+
+ /// Description for the Feed Density Change analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user adjusts the information density of the feed.'**
+ String get analyticsEventFeedDensityChangedDescription;
+
+ /// Label for the Browser Preference Change analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Browser Preference Change'**
+ String get analyticsEventBrowserChoiceChangedLabel;
+
+ /// Description for the Browser Preference Change analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user changes their preferred browser for opening links.'**
+ String get analyticsEventBrowserChoiceChangedDescription;
+
+ /// Label for the Source Filter Usage analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Source Filter Usage'**
+ String get analyticsEventSourceFilterUsedLabel;
+
+ /// Description for the Source Filter Usage analytics event
+ ///
+ /// In en, this message translates to:
+ /// **'Track when a user applies a saved source filter.'**
+ String get analyticsEventSourceFilterUsedDescription;
}
class _AppLocalizationsDelegate
diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart
index cab4677f..a10b849b 100644
--- a/lib/l10n/app_localizations_ar.dart
+++ b/lib/l10n/app_localizations_ar.dart
@@ -2361,4 +2361,311 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get followUpActionsDescription =>
'تكوين ما يحدث بعد استجابة المستخدم لموجه الاستمتاع، مثل طلب مراجعة المتجر.';
+
+ @override
+ String get analyticsTab => 'التحليلات';
+
+ @override
+ String get analyticsDescription =>
+ 'تكوين مزود التحليلات، وتسجيل الأحداث، ومعدلات أخذ العينات.';
+
+ @override
+ String get analyticsSystemStatusTitle => 'تفعيل نظام التحليلات';
+
+ @override
+ String get analyticsSystemStatusDescription =>
+ 'مفتاح رئيسي لتفعيل أو تعطيل جميع عمليات تتبع التحليلات.';
+
+ @override
+ String get analyticsProviderTitle => 'المزود النشط';
+
+ @override
+ String get analyticsProviderDescription =>
+ 'اختر مزود خدمة التحليلات الأساسي.';
+
+ @override
+ String get analyticsEventsTitle => 'تكوين الأحداث';
+
+ @override
+ String get analyticsEventsDescription =>
+ 'ضبط دقيق لتسجيل أحداث محددة. قم بتعطيل الأحداث المزعجة أو تعديل معدلات أخذ العينات.';
+
+ @override
+ String samplingRateLabel(int rate) {
+ return 'معدل أخذ العينات: $rate%';
+ }
+
+ @override
+ String get analyticsProviderFirebase => 'Firebase';
+
+ @override
+ String get analyticsProviderMixpanel => 'Mixpanel';
+
+ @override
+ String get analyticsEventUserRegisteredLabel => 'تسجيل مستخدم جديد';
+
+ @override
+ String get analyticsEventUserRegisteredDescription =>
+ 'تتبع عندما يقوم مستخدم جديد بإنشاء حساب بنجاح.';
+
+ @override
+ String get analyticsEventUserLoginLabel => 'تسجيل دخول المستخدم';
+
+ @override
+ String get analyticsEventUserLoginDescription =>
+ 'تتبع عندما يقوم المستخدم بتسجيل الدخول إلى التطبيق.';
+
+ @override
+ String get analyticsEventAccountLinkedLabel => 'ربط الحساب';
+
+ @override
+ String get analyticsEventAccountLinkedDescription =>
+ 'تتبع عندما يقوم مستخدم ضيف بربط حسابه ببيانات اعتماد دائمة.';
+
+ @override
+ String get analyticsEventUserRoleChangedLabel => 'تغيير دور المستخدم';
+
+ @override
+ String get analyticsEventUserRoleChangedDescription =>
+ 'تتبع عندما يتم تحديث دور المستخدم (مثل الترقية إلى مميز).';
+
+ @override
+ String get analyticsEventContentViewedLabel => 'مشاهدة المحتوى';
+
+ @override
+ String get analyticsEventContentViewedDescription =>
+ 'تتبع عندما يشاهد المستخدم عنوانًا أو مقالًا.';
+
+ @override
+ String get analyticsEventContentSharedLabel => 'مشاركة المحتوى';
+
+ @override
+ String get analyticsEventContentSharedDescription =>
+ 'تتبع عندما يشارك المستخدم المحتوى عبر منصات خارجية.';
+
+ @override
+ String get analyticsEventContentSavedLabel => 'حفظ المحتوى';
+
+ @override
+ String get analyticsEventContentSavedDescription =>
+ 'تتبع عندما يقوم المستخدم بحفظ عنوان في المفضلة.';
+
+ @override
+ String get analyticsEventContentUnsavedLabel => 'إلغاء حفظ المحتوى';
+
+ @override
+ String get analyticsEventContentUnsavedDescription =>
+ 'تتبع عندما يزيل المستخدم عنوانًا من المفضلة.';
+
+ @override
+ String get analyticsEventContentReadingTimeLabel => 'وقت قراءة المحتوى';
+
+ @override
+ String get analyticsEventContentReadingTimeDescription =>
+ 'تتبع المدة التي يقضيها المستخدم في قراءة مقال.';
+
+ @override
+ String get analyticsEventReactionCreatedLabel => 'إضافة رد فعل';
+
+ @override
+ String get analyticsEventReactionCreatedDescription =>
+ 'تتبع عندما يتفاعل المستخدم مع المحتوى.';
+
+ @override
+ String get analyticsEventReactionDeletedLabel => 'إزالة رد فعل';
+
+ @override
+ String get analyticsEventReactionDeletedDescription =>
+ 'تتبع عندما يزيل المستخدم رد فعله.';
+
+ @override
+ String get analyticsEventCommentCreatedLabel => 'نشر تعليق';
+
+ @override
+ String get analyticsEventCommentCreatedDescription =>
+ 'تتبع عندما يرسل المستخدم تعليقًا جديدًا.';
+
+ @override
+ String get analyticsEventCommentDeletedLabel => 'حذف تعليق';
+
+ @override
+ String get analyticsEventCommentDeletedDescription =>
+ 'تتبع عندما يحذف المستخدم تعليقه الخاص.';
+
+ @override
+ String get analyticsEventReportSubmittedLabel => 'تقديم بلاغ';
+
+ @override
+ String get analyticsEventReportSubmittedDescription =>
+ 'تتبع عندما يبلغ المستخدم عن محتوى أو مستخدمين آخرين.';
+
+ @override
+ String get analyticsEventHeadlineFilterCreatedLabel => 'إنشاء مرشح عناوين';
+
+ @override
+ String get analyticsEventHeadlineFilterCreatedDescription =>
+ 'تتبع عندما ينشئ المستخدم مرشح عناوين مخصص جديد.';
+
+ @override
+ String get analyticsEventHeadlineFilterUpdatedLabel => 'تحديث مرشح عناوين';
+
+ @override
+ String get analyticsEventHeadlineFilterUpdatedDescription =>
+ 'تتبع عندما يعدل المستخدم مرشح عناوين موجود.';
+
+ @override
+ String get analyticsEventHeadlineFilterUsedLabel => 'استخدام مرشح عناوين';
+
+ @override
+ String get analyticsEventHeadlineFilterUsedDescription =>
+ 'تتبع عندما يطبق المستخدم مرشح عناوين محفوظ.';
+
+ @override
+ String get analyticsEventSourceFilterCreatedLabel => 'إنشاء مرشح مصادر';
+
+ @override
+ String get analyticsEventSourceFilterCreatedDescription =>
+ 'تتبع عندما ينشئ المستخدم مرشح مصادر جديد.';
+
+ @override
+ String get analyticsEventSourceFilterUpdatedLabel => 'تحديث مرشح مصادر';
+
+ @override
+ String get analyticsEventSourceFilterUpdatedDescription =>
+ 'تتبع عندما يعدل المستخدم مرشح مصادر موجود.';
+
+ @override
+ String get analyticsEventSearchPerformedLabel => 'إجراء بحث';
+
+ @override
+ String get analyticsEventSearchPerformedDescription =>
+ 'تتبع عندما يقوم المستخدم بإجراء استعلام بحث.';
+
+ @override
+ String get analyticsEventAppReviewPromptRespondedLabel =>
+ 'استجابة لطلب التقييم';
+
+ @override
+ String get analyticsEventAppReviewPromptRespondedDescription =>
+ 'تتبع استجابات المستخدم للموجه الداخلي \'هل تستمتع بالتطبيق؟\'.';
+
+ @override
+ String get analyticsEventAppReviewStoreRequestedLabel => 'طلب تقييم المتجر';
+
+ @override
+ String get analyticsEventAppReviewStoreRequestedDescription =>
+ 'تتبع عندما يتم طلب مربع حوار مراجعة المتجر الأصلي لنظام التشغيل.';
+
+ @override
+ String get analyticsEventLimitExceededLabel => 'تجاوز الحد';
+
+ @override
+ String get analyticsEventLimitExceededDescription =>
+ 'تتبع عندما يصل المستخدم إلى حد الاستخدام (مثل حد العناصر المحفوظة).';
+
+ @override
+ String get analyticsEventLimitExceededCtaClickedLabel =>
+ 'النقر على إجراء الحد';
+
+ @override
+ String get analyticsEventLimitExceededCtaClickedDescription =>
+ 'تتبع النقرات على أزرار \'ترقية\' أو \'ربط الحساب\' في مربعات حوار الحدود.';
+
+ @override
+ String get analyticsEventPaywallPresentedLabel => 'ظهور جدار الدفع';
+
+ @override
+ String get analyticsEventPaywallPresentedDescription =>
+ 'تتبع عندما يتم عرض شاشة جدار الدفع للمستخدم.';
+
+ @override
+ String get analyticsEventSubscriptionStartedLabel => 'بدء الاشتراك';
+
+ @override
+ String get analyticsEventSubscriptionStartedDescription =>
+ 'تتبع عندما يبدأ المستخدم اشتراكًا جديدًا بنجاح.';
+
+ @override
+ String get analyticsEventSubscriptionRenewedLabel => 'تجديد الاشتراك';
+
+ @override
+ String get analyticsEventSubscriptionRenewedDescription =>
+ 'تتبع عندما يتم تجديد الاشتراك تلقائيًا أو يدويًا.';
+
+ @override
+ String get analyticsEventSubscriptionCancelledLabel => 'إلغاء الاشتراك';
+
+ @override
+ String get analyticsEventSubscriptionCancelledDescription =>
+ 'تتبع عندما يقوم المستخدم بإلغاء اشتراكه.';
+
+ @override
+ String get analyticsEventSubscriptionEndedLabel => 'انتهاء الاشتراك';
+
+ @override
+ String get analyticsEventSubscriptionEndedDescription =>
+ 'تتبع عندما تنتهي صلاحية الاشتراك أو يتم إنهاؤه.';
+
+ @override
+ String get analyticsEventAdImpressionLabel => 'ظهور إعلان';
+
+ @override
+ String get analyticsEventAdImpressionDescription =>
+ 'تتبع عندما يتم عرض إعلان للمستخدم.';
+
+ @override
+ String get analyticsEventAdClickedLabel => 'النقر على إعلان';
+
+ @override
+ String get analyticsEventAdClickedDescription =>
+ 'تتبع عندما ينقر المستخدم على إعلان.';
+
+ @override
+ String get analyticsEventAdLoadFailedLabel => 'فشل تحميل الإعلان';
+
+ @override
+ String get analyticsEventAdLoadFailedDescription =>
+ 'تتبع الأخطاء عند محاولة تحميل الإعلانات.';
+
+ @override
+ String get analyticsEventAdRewardEarnedLabel => 'كسب مكافأة الإعلان';
+
+ @override
+ String get analyticsEventAdRewardEarnedDescription =>
+ 'تتبع عندما يكمل المستخدم إجراء إعلان بمكافأة.';
+
+ @override
+ String get analyticsEventThemeChangedLabel => 'تغيير السمة';
+
+ @override
+ String get analyticsEventThemeChangedDescription =>
+ 'تتبع عندما يغير المستخدم سمة التطبيق.';
+
+ @override
+ String get analyticsEventLanguageChangedLabel => 'تغيير اللغة';
+
+ @override
+ String get analyticsEventLanguageChangedDescription =>
+ 'تتبع عندما يغير المستخدم لغة التطبيق.';
+
+ @override
+ String get analyticsEventFeedDensityChangedLabel => 'تغيير كثافة الموجز';
+
+ @override
+ String get analyticsEventFeedDensityChangedDescription =>
+ 'تتبع عندما يعدل المستخدم كثافة المعلومات في الموجز.';
+
+ @override
+ String get analyticsEventBrowserChoiceChangedLabel => 'تغيير تفضيل المتصفح';
+
+ @override
+ String get analyticsEventBrowserChoiceChangedDescription =>
+ 'تتبع عندما يغير المستخدم متصفحه المفضل لفتح الروابط.';
+
+ @override
+ String get analyticsEventSourceFilterUsedLabel => 'استخدام مرشح مصادر';
+
+ @override
+ String get analyticsEventSourceFilterUsedDescription =>
+ 'تتبع عندما يطبق المستخدم مرشح مصادر محفوظ.';
}
diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart
index f7234eca..1c598751 100644
--- a/lib/l10n/app_localizations_en.dart
+++ b/lib/l10n/app_localizations_en.dart
@@ -2367,4 +2367,315 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get followUpActionsDescription =>
'Configure what happens after a user responds to the enjoyment prompt, such as requesting a store review.';
+
+ @override
+ String get analyticsTab => 'Analytics';
+
+ @override
+ String get analyticsDescription =>
+ 'Configure analytics provider, event logging, and sampling rates.';
+
+ @override
+ String get analyticsSystemStatusTitle => 'Enable Analytics System';
+
+ @override
+ String get analyticsSystemStatusDescription =>
+ 'Master switch to enable or disable all analytics tracking.';
+
+ @override
+ String get analyticsProviderTitle => 'Active Provider';
+
+ @override
+ String get analyticsProviderDescription =>
+ 'Select the primary analytics service provider.';
+
+ @override
+ String get analyticsEventsTitle => 'Event Configuration';
+
+ @override
+ String get analyticsEventsDescription =>
+ 'Fine-tune logging for specific events. Disable noisy events or adjust sampling rates.';
+
+ @override
+ String samplingRateLabel(int rate) {
+ return 'Sampling Rate: $rate%';
+ }
+
+ @override
+ String get analyticsProviderFirebase => 'Firebase';
+
+ @override
+ String get analyticsProviderMixpanel => 'Mixpanel';
+
+ @override
+ String get analyticsEventUserRegisteredLabel => 'User Registration';
+
+ @override
+ String get analyticsEventUserRegisteredDescription =>
+ 'Track when a new user successfully creates an account.';
+
+ @override
+ String get analyticsEventUserLoginLabel => 'User Login';
+
+ @override
+ String get analyticsEventUserLoginDescription =>
+ 'Track when a user logs in to the application.';
+
+ @override
+ String get analyticsEventAccountLinkedLabel => 'Account Linking';
+
+ @override
+ String get analyticsEventAccountLinkedDescription =>
+ 'Track when a guest user links their account to a permanent credential.';
+
+ @override
+ String get analyticsEventUserRoleChangedLabel => 'User Role Change';
+
+ @override
+ String get analyticsEventUserRoleChangedDescription =>
+ 'Track when a user\'s role is updated (e.g., upgraded to Premium).';
+
+ @override
+ String get analyticsEventContentViewedLabel => 'Content View';
+
+ @override
+ String get analyticsEventContentViewedDescription =>
+ 'Track when a user views a headline or article.';
+
+ @override
+ String get analyticsEventContentSharedLabel => 'Content Share';
+
+ @override
+ String get analyticsEventContentSharedDescription =>
+ 'Track when a user shares content via external platforms.';
+
+ @override
+ String get analyticsEventContentSavedLabel => 'Content Save';
+
+ @override
+ String get analyticsEventContentSavedDescription =>
+ 'Track when a user bookmarks a headline.';
+
+ @override
+ String get analyticsEventContentUnsavedLabel => 'Content Unsave';
+
+ @override
+ String get analyticsEventContentUnsavedDescription =>
+ 'Track when a user removes a bookmark.';
+
+ @override
+ String get analyticsEventContentReadingTimeLabel => 'Content Reading Time';
+
+ @override
+ String get analyticsEventContentReadingTimeDescription =>
+ 'Track the duration a user spends reading an article.';
+
+ @override
+ String get analyticsEventReactionCreatedLabel => 'Reaction Added';
+
+ @override
+ String get analyticsEventReactionCreatedDescription =>
+ 'Track when a user reacts to content.';
+
+ @override
+ String get analyticsEventReactionDeletedLabel => 'Reaction Removed';
+
+ @override
+ String get analyticsEventReactionDeletedDescription =>
+ 'Track when a user removes their reaction.';
+
+ @override
+ String get analyticsEventCommentCreatedLabel => 'Comment Posted';
+
+ @override
+ String get analyticsEventCommentCreatedDescription =>
+ 'Track when a user submits a new comment.';
+
+ @override
+ String get analyticsEventCommentDeletedLabel => 'Comment Deleted';
+
+ @override
+ String get analyticsEventCommentDeletedDescription =>
+ 'Track when a user deletes their own comment.';
+
+ @override
+ String get analyticsEventReportSubmittedLabel => 'Report Submitted';
+
+ @override
+ String get analyticsEventReportSubmittedDescription =>
+ 'Track when a user reports content or other users.';
+
+ @override
+ String get analyticsEventHeadlineFilterCreatedLabel =>
+ 'Headline Filter Creation';
+
+ @override
+ String get analyticsEventHeadlineFilterCreatedDescription =>
+ 'Track when a user creates a new custom headline filter.';
+
+ @override
+ String get analyticsEventHeadlineFilterUpdatedLabel =>
+ 'Headline Filter Update';
+
+ @override
+ String get analyticsEventHeadlineFilterUpdatedDescription =>
+ 'Track when a user modifies an existing headline filter.';
+
+ @override
+ String get analyticsEventHeadlineFilterUsedLabel => 'Headline Filter Usage';
+
+ @override
+ String get analyticsEventHeadlineFilterUsedDescription =>
+ 'Track when a user applies a saved headline filter.';
+
+ @override
+ String get analyticsEventSourceFilterCreatedLabel => 'Source Filter Creation';
+
+ @override
+ String get analyticsEventSourceFilterCreatedDescription =>
+ 'Track when a user creates a new source filter.';
+
+ @override
+ String get analyticsEventSourceFilterUpdatedLabel => 'Source Filter Update';
+
+ @override
+ String get analyticsEventSourceFilterUpdatedDescription =>
+ 'Track when a user modifies an existing source filter.';
+
+ @override
+ String get analyticsEventSearchPerformedLabel => 'Search Performed';
+
+ @override
+ String get analyticsEventSearchPerformedDescription =>
+ 'Track when a user performs a search query.';
+
+ @override
+ String get analyticsEventAppReviewPromptRespondedLabel =>
+ 'Review Prompt Response';
+
+ @override
+ String get analyticsEventAppReviewPromptRespondedDescription =>
+ 'Track user responses to the internal \'Enjoying the app?\' prompt.';
+
+ @override
+ String get analyticsEventAppReviewStoreRequestedLabel =>
+ 'Store Review Request';
+
+ @override
+ String get analyticsEventAppReviewStoreRequestedDescription =>
+ 'Track when the native OS store review dialog is requested.';
+
+ @override
+ String get analyticsEventLimitExceededLabel => 'Limit Exceeded';
+
+ @override
+ String get analyticsEventLimitExceededDescription =>
+ 'Track when a user hits a usage limit (e.g., saved items limit).';
+
+ @override
+ String get analyticsEventLimitExceededCtaClickedLabel => 'Limit CTA Click';
+
+ @override
+ String get analyticsEventLimitExceededCtaClickedDescription =>
+ 'Track clicks on \'Upgrade\' or \'Link Account\' buttons in limit dialogs.';
+
+ @override
+ String get analyticsEventPaywallPresentedLabel => 'Paywall Impression';
+
+ @override
+ String get analyticsEventPaywallPresentedDescription =>
+ 'Track when the paywall screen is shown to a user.';
+
+ @override
+ String get analyticsEventSubscriptionStartedLabel => 'Subscription Start';
+
+ @override
+ String get analyticsEventSubscriptionStartedDescription =>
+ 'Track when a user successfully starts a new subscription.';
+
+ @override
+ String get analyticsEventSubscriptionRenewedLabel => 'Subscription Renewal';
+
+ @override
+ String get analyticsEventSubscriptionRenewedDescription =>
+ 'Track when a subscription is automatically or manually renewed.';
+
+ @override
+ String get analyticsEventSubscriptionCancelledLabel =>
+ 'Subscription Cancellation';
+
+ @override
+ String get analyticsEventSubscriptionCancelledDescription =>
+ 'Track when a user cancels their subscription.';
+
+ @override
+ String get analyticsEventSubscriptionEndedLabel => 'Subscription End';
+
+ @override
+ String get analyticsEventSubscriptionEndedDescription =>
+ 'Track when a subscription expires or is terminated.';
+
+ @override
+ String get analyticsEventAdImpressionLabel => 'Ad Impression';
+
+ @override
+ String get analyticsEventAdImpressionDescription =>
+ 'Track when an advertisement is displayed to the user.';
+
+ @override
+ String get analyticsEventAdClickedLabel => 'Ad Click';
+
+ @override
+ String get analyticsEventAdClickedDescription =>
+ 'Track when a user clicks on an advertisement.';
+
+ @override
+ String get analyticsEventAdLoadFailedLabel => 'Ad Load Failure';
+
+ @override
+ String get analyticsEventAdLoadFailedDescription =>
+ 'Track errors when attempting to load advertisements.';
+
+ @override
+ String get analyticsEventAdRewardEarnedLabel => 'Ad Reward Earned';
+
+ @override
+ String get analyticsEventAdRewardEarnedDescription =>
+ 'Track when a user completes a rewarded ad action.';
+
+ @override
+ String get analyticsEventThemeChangedLabel => 'Theme Change';
+
+ @override
+ String get analyticsEventThemeChangedDescription =>
+ 'Track when a user changes the application theme.';
+
+ @override
+ String get analyticsEventLanguageChangedLabel => 'Language Change';
+
+ @override
+ String get analyticsEventLanguageChangedDescription =>
+ 'Track when a user changes the application language.';
+
+ @override
+ String get analyticsEventFeedDensityChangedLabel => 'Feed Density Change';
+
+ @override
+ String get analyticsEventFeedDensityChangedDescription =>
+ 'Track when a user adjusts the information density of the feed.';
+
+ @override
+ String get analyticsEventBrowserChoiceChangedLabel =>
+ 'Browser Preference Change';
+
+ @override
+ String get analyticsEventBrowserChoiceChangedDescription =>
+ 'Track when a user changes their preferred browser for opening links.';
+
+ @override
+ String get analyticsEventSourceFilterUsedLabel => 'Source Filter Usage';
+
+ @override
+ String get analyticsEventSourceFilterUsedDescription =>
+ 'Track when a user applies a saved source filter.';
}
diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb
index 6844f8ee..e6013b26 100644
--- a/lib/l10n/arb/app_ar.arb
+++ b/lib/l10n/arb/app_ar.arb
@@ -2991,5 +2991,358 @@
"followUpActionsDescription": "تكوين ما يحدث بعد استجابة المستخدم لموجه الاستمتاع، مثل طلب مراجعة المتجر.",
"@followUpActionsDescription": {
"description": "وصف لإعدادات إجراءات المتابعة."
+ },
+ "analyticsTab": "التحليلات",
+ "@analyticsTab": {
+ "description": "عنوان تبويب إعدادات التحليلات"
+ },
+ "analyticsDescription": "تكوين مزود التحليلات، وتسجيل الأحداث، ومعدلات أخذ العينات.",
+ "@analyticsDescription": {
+ "description": "وصف لقسم التحليلات القابل للتوسيع"
+ },
+ "analyticsSystemStatusTitle": "تفعيل نظام التحليلات",
+ "@analyticsSystemStatusTitle": {
+ "description": "عنوان قسم حالة نظام التحليلات"
+ },
+ "analyticsSystemStatusDescription": "مفتاح رئيسي لتفعيل أو تعطيل جميع عمليات تتبع التحليلات.",
+ "@analyticsSystemStatusDescription": {
+ "description": "وصف قسم حالة نظام التحليلات"
+ },
+ "analyticsProviderTitle": "المزود النشط",
+ "@analyticsProviderTitle": {
+ "description": "عنوان قسم مزود التحليلات"
+ },
+ "analyticsProviderDescription": "اختر مزود خدمة التحليلات الأساسي.",
+ "@analyticsProviderDescription": {
+ "description": "وصف قسم مزود التحليلات"
+ },
+ "analyticsEventsTitle": "تكوين الأحداث",
+ "@analyticsEventsTitle": {
+ "description": "عنوان قسم تكوين الأحداث"
+ },
+ "analyticsEventsDescription": "ضبط دقيق لتسجيل أحداث محددة. قم بتعطيل الأحداث المزعجة أو تعديل معدلات أخذ العينات.",
+ "@analyticsEventsDescription": {
+ "description": "وصف قسم تكوين الأحداث"
+ },
+ "samplingRateLabel": "معدل أخذ العينات: {rate}%",
+ "@samplingRateLabel": {
+ "description": "تسمية لشريط تمرير معدل أخذ العينات",
+ "placeholders": {
+ "rate": {
+ "type": "int"
+ }
+ }
+ },
+ "analyticsProviderFirebase": "Firebase",
+ "@analyticsProviderFirebase": {
+ "description": "تسمية مزود التحليلات Firebase"
+ },
+ "analyticsProviderMixpanel": "Mixpanel",
+ "@analyticsProviderMixpanel": {
+ "description": "تسمية مزود التحليلات Mixpanel"
+ },
+ "analyticsEventUserRegisteredLabel": "تسجيل مستخدم جديد",
+ "@analyticsEventUserRegisteredLabel": {
+ "description": "تسمية لحدث تسجيل مستخدم جديد"
+ },
+ "analyticsEventUserRegisteredDescription": "تتبع عندما يقوم مستخدم جديد بإنشاء حساب بنجاح.",
+ "@analyticsEventUserRegisteredDescription": {
+ "description": "وصف لحدث تسجيل مستخدم جديد"
+ },
+ "analyticsEventUserLoginLabel": "تسجيل دخول المستخدم",
+ "@analyticsEventUserLoginLabel": {
+ "description": "تسمية لحدث تسجيل دخول المستخدم"
+ },
+ "analyticsEventUserLoginDescription": "تتبع عندما يقوم المستخدم بتسجيل الدخول إلى التطبيق.",
+ "@analyticsEventUserLoginDescription": {
+ "description": "وصف لحدث تسجيل دخول المستخدم"
+ },
+ "analyticsEventAccountLinkedLabel": "ربط الحساب",
+ "@analyticsEventAccountLinkedLabel": {
+ "description": "تسمية لحدث ربط الحساب"
+ },
+ "analyticsEventAccountLinkedDescription": "تتبع عندما يقوم مستخدم ضيف بربط حسابه ببيانات اعتماد دائمة.",
+ "@analyticsEventAccountLinkedDescription": {
+ "description": "وصف لحدث ربط الحساب"
+ },
+ "analyticsEventUserRoleChangedLabel": "تغيير دور المستخدم",
+ "@analyticsEventUserRoleChangedLabel": {
+ "description": "تسمية لحدث تغيير دور المستخدم"
+ },
+ "analyticsEventUserRoleChangedDescription": "تتبع عندما يتم تحديث دور المستخدم (مثل الترقية إلى مميز).",
+ "@analyticsEventUserRoleChangedDescription": {
+ "description": "وصف لحدث تغيير دور المستخدم"
+ },
+ "analyticsEventContentViewedLabel": "مشاهدة المحتوى",
+ "@analyticsEventContentViewedLabel": {
+ "description": "تسمية لحدث مشاهدة المحتوى"
+ },
+ "analyticsEventContentViewedDescription": "تتبع عندما يشاهد المستخدم عنوانًا أو مقالًا.",
+ "@analyticsEventContentViewedDescription": {
+ "description": "وصف لحدث مشاهدة المحتوى"
+ },
+ "analyticsEventContentSharedLabel": "مشاركة المحتوى",
+ "@analyticsEventContentSharedLabel": {
+ "description": "تسمية لحدث مشاركة المحتوى"
+ },
+ "analyticsEventContentSharedDescription": "تتبع عندما يشارك المستخدم المحتوى عبر منصات خارجية.",
+ "@analyticsEventContentSharedDescription": {
+ "description": "وصف لحدث مشاركة المحتوى"
+ },
+ "analyticsEventContentSavedLabel": "حفظ المحتوى",
+ "@analyticsEventContentSavedLabel": {
+ "description": "تسمية لحدث حفظ المحتوى"
+ },
+ "analyticsEventContentSavedDescription": "تتبع عندما يقوم المستخدم بحفظ عنوان في المفضلة.",
+ "@analyticsEventContentSavedDescription": {
+ "description": "وصف لحدث حفظ المحتوى"
+ },
+ "analyticsEventContentUnsavedLabel": "إلغاء حفظ المحتوى",
+ "@analyticsEventContentUnsavedLabel": {
+ "description": "تسمية لحدث إلغاء حفظ المحتوى"
+ },
+ "analyticsEventContentUnsavedDescription": "تتبع عندما يزيل المستخدم عنوانًا من المفضلة.",
+ "@analyticsEventContentUnsavedDescription": {
+ "description": "وصف لحدث إلغاء حفظ المحتوى"
+ },
+ "analyticsEventContentReadingTimeLabel": "وقت قراءة المحتوى",
+ "@analyticsEventContentReadingTimeLabel": {
+ "description": "تسمية لحدث وقت قراءة المحتوى"
+ },
+ "analyticsEventContentReadingTimeDescription": "تتبع المدة التي يقضيها المستخدم في قراءة مقال.",
+ "@analyticsEventContentReadingTimeDescription": {
+ "description": "وصف لحدث وقت قراءة المحتوى"
+ },
+ "analyticsEventReactionCreatedLabel": "إضافة رد فعل",
+ "@analyticsEventReactionCreatedLabel": {
+ "description": "تسمية لحدث إضافة رد فعل"
+ },
+ "analyticsEventReactionCreatedDescription": "تتبع عندما يتفاعل المستخدم مع المحتوى.",
+ "@analyticsEventReactionCreatedDescription": {
+ "description": "وصف لحدث إضافة رد فعل"
+ },
+ "analyticsEventReactionDeletedLabel": "إزالة رد فعل",
+ "@analyticsEventReactionDeletedLabel": {
+ "description": "تسمية لحدث إزالة رد فعل"
+ },
+ "analyticsEventReactionDeletedDescription": "تتبع عندما يزيل المستخدم رد فعله.",
+ "@analyticsEventReactionDeletedDescription": {
+ "description": "وصف لحدث إزالة رد فعل"
+ },
+ "analyticsEventCommentCreatedLabel": "نشر تعليق",
+ "@analyticsEventCommentCreatedLabel": {
+ "description": "تسمية لحدث نشر تعليق"
+ },
+ "analyticsEventCommentCreatedDescription": "تتبع عندما يرسل المستخدم تعليقًا جديدًا.",
+ "@analyticsEventCommentCreatedDescription": {
+ "description": "وصف لحدث نشر تعليق"
+ },
+ "analyticsEventCommentDeletedLabel": "حذف تعليق",
+ "@analyticsEventCommentDeletedLabel": {
+ "description": "تسمية لحدث حذف تعليق"
+ },
+ "analyticsEventCommentDeletedDescription": "تتبع عندما يحذف المستخدم تعليقه الخاص.",
+ "@analyticsEventCommentDeletedDescription": {
+ "description": "وصف لحدث حذف تعليق"
+ },
+ "analyticsEventReportSubmittedLabel": "تقديم بلاغ",
+ "@analyticsEventReportSubmittedLabel": {
+ "description": "تسمية لحدث تقديم بلاغ"
+ },
+ "analyticsEventReportSubmittedDescription": "تتبع عندما يبلغ المستخدم عن محتوى أو مستخدمين آخرين.",
+ "@analyticsEventReportSubmittedDescription": {
+ "description": "وصف لحدث تقديم بلاغ"
+ },
+ "analyticsEventHeadlineFilterCreatedLabel": "إنشاء مرشح عناوين",
+ "@analyticsEventHeadlineFilterCreatedLabel": {
+ "description": "تسمية لحدث إنشاء مرشح عناوين"
+ },
+ "analyticsEventHeadlineFilterCreatedDescription": "تتبع عندما ينشئ المستخدم مرشح عناوين مخصص جديد.",
+ "@analyticsEventHeadlineFilterCreatedDescription": {
+ "description": "وصف لحدث إنشاء مرشح عناوين"
+ },
+ "analyticsEventHeadlineFilterUpdatedLabel": "تحديث مرشح عناوين",
+ "@analyticsEventHeadlineFilterUpdatedLabel": {
+ "description": "تسمية لحدث تحديث مرشح عناوين"
+ },
+ "analyticsEventHeadlineFilterUpdatedDescription": "تتبع عندما يعدل المستخدم مرشح عناوين موجود.",
+ "@analyticsEventHeadlineFilterUpdatedDescription": {
+ "description": "وصف لحدث تحديث مرشح عناوين"
+ },
+ "analyticsEventHeadlineFilterUsedLabel": "استخدام مرشح عناوين",
+ "@analyticsEventHeadlineFilterUsedLabel": {
+ "description": "تسمية لحدث استخدام مرشح عناوين"
+ },
+ "analyticsEventHeadlineFilterUsedDescription": "تتبع عندما يطبق المستخدم مرشح عناوين محفوظ.",
+ "@analyticsEventHeadlineFilterUsedDescription": {
+ "description": "وصف لحدث استخدام مرشح عناوين"
+ },
+ "analyticsEventSourceFilterCreatedLabel": "إنشاء مرشح مصادر",
+ "@analyticsEventSourceFilterCreatedLabel": {
+ "description": "تسمية لحدث إنشاء مرشح مصادر"
+ },
+ "analyticsEventSourceFilterCreatedDescription": "تتبع عندما ينشئ المستخدم مرشح مصادر جديد.",
+ "@analyticsEventSourceFilterCreatedDescription": {
+ "description": "وصف لحدث إنشاء مرشح مصادر"
+ },
+ "analyticsEventSourceFilterUpdatedLabel": "تحديث مرشح مصادر",
+ "@analyticsEventSourceFilterUpdatedLabel": {
+ "description": "تسمية لحدث تحديث مرشح مصادر"
+ },
+ "analyticsEventSourceFilterUpdatedDescription": "تتبع عندما يعدل المستخدم مرشح مصادر موجود.",
+ "@analyticsEventSourceFilterUpdatedDescription": {
+ "description": "وصف لحدث تحديث مرشح مصادر"
+ },
+ "analyticsEventSearchPerformedLabel": "إجراء بحث",
+ "@analyticsEventSearchPerformedLabel": {
+ "description": "تسمية لحدث إجراء بحث"
+ },
+ "analyticsEventSearchPerformedDescription": "تتبع عندما يقوم المستخدم بإجراء استعلام بحث.",
+ "@analyticsEventSearchPerformedDescription": {
+ "description": "وصف لحدث إجراء بحث"
+ },
+ "analyticsEventAppReviewPromptRespondedLabel": "استجابة لطلب التقييم",
+ "@analyticsEventAppReviewPromptRespondedLabel": {
+ "description": "تسمية لحدث استجابة لطلب التقييم"
+ },
+ "analyticsEventAppReviewPromptRespondedDescription": "تتبع استجابات المستخدم للموجه الداخلي 'هل تستمتع بالتطبيق؟'.",
+ "@analyticsEventAppReviewPromptRespondedDescription": {
+ "description": "وصف لحدث استجابة لطلب التقييم"
+ },
+ "analyticsEventAppReviewStoreRequestedLabel": "طلب تقييم المتجر",
+ "@analyticsEventAppReviewStoreRequestedLabel": {
+ "description": "تسمية لحدث طلب تقييم المتجر"
+ },
+ "analyticsEventAppReviewStoreRequestedDescription": "تتبع عندما يتم طلب مربع حوار مراجعة المتجر الأصلي لنظام التشغيل.",
+ "@analyticsEventAppReviewStoreRequestedDescription": {
+ "description": "وصف لحدث طلب تقييم المتجر"
+ },
+ "analyticsEventLimitExceededLabel": "تجاوز الحد",
+ "@analyticsEventLimitExceededLabel": {
+ "description": "تسمية لحدث تجاوز الحد"
+ },
+ "analyticsEventLimitExceededDescription": "تتبع عندما يصل المستخدم إلى حد الاستخدام (مثل حد العناصر المحفوظة).",
+ "@analyticsEventLimitExceededDescription": {
+ "description": "وصف لحدث تجاوز الحد"
+ },
+ "analyticsEventLimitExceededCtaClickedLabel": "النقر على إجراء الحد",
+ "@analyticsEventLimitExceededCtaClickedLabel": {
+ "description": "تسمية لحدث النقر على إجراء الحد"
+ },
+ "analyticsEventLimitExceededCtaClickedDescription": "تتبع النقرات على أزرار 'ترقية' أو 'ربط الحساب' في مربعات حوار الحدود.",
+ "@analyticsEventLimitExceededCtaClickedDescription": {
+ "description": "وصف لحدث النقر على إجراء الحد"
+ },
+ "analyticsEventPaywallPresentedLabel": "ظهور جدار الدفع",
+ "@analyticsEventPaywallPresentedLabel": {
+ "description": "تسمية لحدث ظهور جدار الدفع"
+ },
+ "analyticsEventPaywallPresentedDescription": "تتبع عندما يتم عرض شاشة جدار الدفع للمستخدم.",
+ "@analyticsEventPaywallPresentedDescription": {
+ "description": "وصف لحدث ظهور جدار الدفع"
+ },
+ "analyticsEventSubscriptionStartedLabel": "بدء الاشتراك",
+ "@analyticsEventSubscriptionStartedLabel": {
+ "description": "تسمية لحدث بدء الاشتراك"
+ },
+ "analyticsEventSubscriptionStartedDescription": "تتبع عندما يبدأ المستخدم اشتراكًا جديدًا بنجاح.",
+ "@analyticsEventSubscriptionStartedDescription": {
+ "description": "وصف لحدث بدء الاشتراك"
+ },
+ "analyticsEventSubscriptionRenewedLabel": "تجديد الاشتراك",
+ "@analyticsEventSubscriptionRenewedLabel": {
+ "description": "تسمية لحدث تجديد الاشتراك"
+ },
+ "analyticsEventSubscriptionRenewedDescription": "تتبع عندما يتم تجديد الاشتراك تلقائيًا أو يدويًا.",
+ "@analyticsEventSubscriptionRenewedDescription": {
+ "description": "وصف لحدث تجديد الاشتراك"
+ },
+ "analyticsEventSubscriptionCancelledLabel": "إلغاء الاشتراك",
+ "@analyticsEventSubscriptionCancelledLabel": {
+ "description": "تسمية لحدث إلغاء الاشتراك"
+ },
+ "analyticsEventSubscriptionCancelledDescription": "تتبع عندما يقوم المستخدم بإلغاء اشتراكه.",
+ "@analyticsEventSubscriptionCancelledDescription": {
+ "description": "وصف لحدث إلغاء الاشتراك"
+ },
+ "analyticsEventSubscriptionEndedLabel": "انتهاء الاشتراك",
+ "@analyticsEventSubscriptionEndedLabel": {
+ "description": "تسمية لحدث انتهاء الاشتراك"
+ },
+ "analyticsEventSubscriptionEndedDescription": "تتبع عندما تنتهي صلاحية الاشتراك أو يتم إنهاؤه.",
+ "@analyticsEventSubscriptionEndedDescription": {
+ "description": "وصف لحدث انتهاء الاشتراك"
+ },
+ "analyticsEventAdImpressionLabel": "ظهور إعلان",
+ "@analyticsEventAdImpressionLabel": {
+ "description": "تسمية لحدث ظهور إعلان"
+ },
+ "analyticsEventAdImpressionDescription": "تتبع عندما يتم عرض إعلان للمستخدم.",
+ "@analyticsEventAdImpressionDescription": {
+ "description": "وصف لحدث ظهور إعلان"
+ },
+ "analyticsEventAdClickedLabel": "النقر على إعلان",
+ "@analyticsEventAdClickedLabel": {
+ "description": "تسمية لحدث النقر على إعلان"
+ },
+ "analyticsEventAdClickedDescription": "تتبع عندما ينقر المستخدم على إعلان.",
+ "@analyticsEventAdClickedDescription": {
+ "description": "وصف لحدث النقر على إعلان"
+ },
+ "analyticsEventAdLoadFailedLabel": "فشل تحميل الإعلان",
+ "@analyticsEventAdLoadFailedLabel": {
+ "description": "تسمية لحدث فشل تحميل الإعلان"
+ },
+ "analyticsEventAdLoadFailedDescription": "تتبع الأخطاء عند محاولة تحميل الإعلانات.",
+ "@analyticsEventAdLoadFailedDescription": {
+ "description": "وصف لحدث فشل تحميل الإعلان"
+ },
+ "analyticsEventAdRewardEarnedLabel": "كسب مكافأة الإعلان",
+ "@analyticsEventAdRewardEarnedLabel": {
+ "description": "تسمية لحدث كسب مكافأة الإعلان"
+ },
+ "analyticsEventAdRewardEarnedDescription": "تتبع عندما يكمل المستخدم إجراء إعلان بمكافأة.",
+ "@analyticsEventAdRewardEarnedDescription": {
+ "description": "وصف لحدث كسب مكافأة الإعلان"
+ },
+ "analyticsEventThemeChangedLabel": "تغيير السمة",
+ "@analyticsEventThemeChangedLabel": {
+ "description": "تسمية لحدث تغيير السمة"
+ },
+ "analyticsEventThemeChangedDescription": "تتبع عندما يغير المستخدم سمة التطبيق.",
+ "@analyticsEventThemeChangedDescription": {
+ "description": "وصف لحدث تغيير السمة"
+ },
+ "analyticsEventLanguageChangedLabel": "تغيير اللغة",
+ "@analyticsEventLanguageChangedLabel": {
+ "description": "تسمية لحدث تغيير اللغة"
+ },
+ "analyticsEventLanguageChangedDescription": "تتبع عندما يغير المستخدم لغة التطبيق.",
+ "@analyticsEventLanguageChangedDescription": {
+ "description": "وصف لحدث تغيير اللغة"
+ },
+ "analyticsEventFeedDensityChangedLabel": "تغيير كثافة الموجز",
+ "@analyticsEventFeedDensityChangedLabel": {
+ "description": "تسمية لحدث تغيير كثافة الموجز"
+ },
+ "analyticsEventFeedDensityChangedDescription": "تتبع عندما يعدل المستخدم كثافة المعلومات في الموجز.",
+ "@analyticsEventFeedDensityChangedDescription": {
+ "description": "وصف لحدث تغيير كثافة الموجز"
+ },
+ "analyticsEventBrowserChoiceChangedLabel": "تغيير تفضيل المتصفح",
+ "@analyticsEventBrowserChoiceChangedLabel": {
+ "description": "تسمية لحدث تغيير تفضيل المتصفح"
+ },
+ "analyticsEventBrowserChoiceChangedDescription": "تتبع عندما يغير المستخدم متصفحه المفضل لفتح الروابط.",
+ "@analyticsEventBrowserChoiceChangedDescription": {
+ "description": "وصف لحدث تغيير تفضيل المتصفح"
+ },
+ "analyticsEventSourceFilterUsedLabel": "استخدام مرشح مصادر",
+ "@analyticsEventSourceFilterUsedLabel": {
+ "description": "تسمية لحدث استخدام مرشح مصادر"
+ },
+ "analyticsEventSourceFilterUsedDescription": "تتبع عندما يطبق المستخدم مرشح مصادر محفوظ.",
+ "@analyticsEventSourceFilterUsedDescription": {
+ "description": "وصف لحدث استخدام مرشح مصادر"
}
}
\ No newline at end of file
diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb
index 97d630c6..b9e14336 100644
--- a/lib/l10n/arb/app_en.arb
+++ b/lib/l10n/arb/app_en.arb
@@ -2987,5 +2987,358 @@
"followUpActionsDescription": "Configure what happens after a user responds to the enjoyment prompt, such as requesting a store review.",
"@followUpActionsDescription": {
"description": "Description for the follow-up actions settings."
+ },
+ "analyticsTab": "Analytics",
+ "@analyticsTab": {
+ "description": "Tab title for Analytics settings"
+ },
+ "analyticsDescription": "Configure analytics provider, event logging, and sampling rates.",
+ "@analyticsDescription": {
+ "description": "Description for the Analytics expansion tile"
+ },
+ "analyticsSystemStatusTitle": "Enable Analytics System",
+ "@analyticsSystemStatusTitle": {
+ "description": "Title for the Analytics System status section"
+ },
+ "analyticsSystemStatusDescription": "Master switch to enable or disable all analytics tracking.",
+ "@analyticsSystemStatusDescription": {
+ "description": "Description for the Analytics System status section"
+ },
+ "analyticsProviderTitle": "Active Provider",
+ "@analyticsProviderTitle": {
+ "description": "Title for the Analytics Provider section"
+ },
+ "analyticsProviderDescription": "Select the primary analytics service provider.",
+ "@analyticsProviderDescription": {
+ "description": "Description for the Analytics Provider section"
+ },
+ "analyticsEventsTitle": "Event Configuration",
+ "@analyticsEventsTitle": {
+ "description": "Title for the Event Configuration section"
+ },
+ "analyticsEventsDescription": "Fine-tune logging for specific events. Disable noisy events or adjust sampling rates.",
+ "@analyticsEventsDescription": {
+ "description": "Description for the Event Configuration section"
+ },
+ "samplingRateLabel": "Sampling Rate: {rate}%",
+ "@samplingRateLabel": {
+ "description": "Label for the sampling rate slider",
+ "placeholders": {
+ "rate": {
+ "type": "int"
+ }
+ }
+ },
+ "analyticsProviderFirebase": "Firebase",
+ "@analyticsProviderFirebase": {
+ "description": "Label for Firebase analytics provider"
+ },
+ "analyticsProviderMixpanel": "Mixpanel",
+ "@analyticsProviderMixpanel": {
+ "description": "Label for Mixpanel analytics provider"
+ },
+ "analyticsEventUserRegisteredLabel": "User Registration",
+ "@analyticsEventUserRegisteredLabel": {
+ "description": "Label for the User Registration analytics event"
+ },
+ "analyticsEventUserRegisteredDescription": "Track when a new user successfully creates an account.",
+ "@analyticsEventUserRegisteredDescription": {
+ "description": "Description for the User Registration analytics event"
+ },
+ "analyticsEventUserLoginLabel": "User Login",
+ "@analyticsEventUserLoginLabel": {
+ "description": "Label for the User Login analytics event"
+ },
+ "analyticsEventUserLoginDescription": "Track when a user logs in to the application.",
+ "@analyticsEventUserLoginDescription": {
+ "description": "Description for the User Login analytics event"
+ },
+ "analyticsEventAccountLinkedLabel": "Account Linking",
+ "@analyticsEventAccountLinkedLabel": {
+ "description": "Label for the Account Linking analytics event"
+ },
+ "analyticsEventAccountLinkedDescription": "Track when a guest user links their account to a permanent credential.",
+ "@analyticsEventAccountLinkedDescription": {
+ "description": "Description for the Account Linking analytics event"
+ },
+ "analyticsEventUserRoleChangedLabel": "User Role Change",
+ "@analyticsEventUserRoleChangedLabel": {
+ "description": "Label for the User Role Change analytics event"
+ },
+ "analyticsEventUserRoleChangedDescription": "Track when a user's role is updated (e.g., upgraded to Premium).",
+ "@analyticsEventUserRoleChangedDescription": {
+ "description": "Description for the User Role Change analytics event"
+ },
+ "analyticsEventContentViewedLabel": "Content View",
+ "@analyticsEventContentViewedLabel": {
+ "description": "Label for the Content View analytics event"
+ },
+ "analyticsEventContentViewedDescription": "Track when a user views a headline or article.",
+ "@analyticsEventContentViewedDescription": {
+ "description": "Description for the Content View analytics event"
+ },
+ "analyticsEventContentSharedLabel": "Content Share",
+ "@analyticsEventContentSharedLabel": {
+ "description": "Label for the Content Share analytics event"
+ },
+ "analyticsEventContentSharedDescription": "Track when a user shares content via external platforms.",
+ "@analyticsEventContentSharedDescription": {
+ "description": "Description for the Content Share analytics event"
+ },
+ "analyticsEventContentSavedLabel": "Content Save",
+ "@analyticsEventContentSavedLabel": {
+ "description": "Label for the Content Save analytics event"
+ },
+ "analyticsEventContentSavedDescription": "Track when a user bookmarks a headline.",
+ "@analyticsEventContentSavedDescription": {
+ "description": "Description for the Content Save analytics event"
+ },
+ "analyticsEventContentUnsavedLabel": "Content Unsave",
+ "@analyticsEventContentUnsavedLabel": {
+ "description": "Label for the Content Unsave analytics event"
+ },
+ "analyticsEventContentUnsavedDescription": "Track when a user removes a bookmark.",
+ "@analyticsEventContentUnsavedDescription": {
+ "description": "Description for the Content Unsave analytics event"
+ },
+ "analyticsEventContentReadingTimeLabel": "Content Reading Time",
+ "@analyticsEventContentReadingTimeLabel": {
+ "description": "Label for the Content Reading Time analytics event"
+ },
+ "analyticsEventContentReadingTimeDescription": "Track the duration a user spends reading an article.",
+ "@analyticsEventContentReadingTimeDescription": {
+ "description": "Description for the Content Reading Time analytics event"
+ },
+ "analyticsEventReactionCreatedLabel": "Reaction Added",
+ "@analyticsEventReactionCreatedLabel": {
+ "description": "Label for the Reaction Added analytics event"
+ },
+ "analyticsEventReactionCreatedDescription": "Track when a user reacts to content.",
+ "@analyticsEventReactionCreatedDescription": {
+ "description": "Description for the Reaction Added analytics event"
+ },
+ "analyticsEventReactionDeletedLabel": "Reaction Removed",
+ "@analyticsEventReactionDeletedLabel": {
+ "description": "Label for the Reaction Removed analytics event"
+ },
+ "analyticsEventReactionDeletedDescription": "Track when a user removes their reaction.",
+ "@analyticsEventReactionDeletedDescription": {
+ "description": "Description for the Reaction Removed analytics event"
+ },
+ "analyticsEventCommentCreatedLabel": "Comment Posted",
+ "@analyticsEventCommentCreatedLabel": {
+ "description": "Label for the Comment Posted analytics event"
+ },
+ "analyticsEventCommentCreatedDescription": "Track when a user submits a new comment.",
+ "@analyticsEventCommentCreatedDescription": {
+ "description": "Description for the Comment Posted analytics event"
+ },
+ "analyticsEventCommentDeletedLabel": "Comment Deleted",
+ "@analyticsEventCommentDeletedLabel": {
+ "description": "Label for the Comment Deleted analytics event"
+ },
+ "analyticsEventCommentDeletedDescription": "Track when a user deletes their own comment.",
+ "@analyticsEventCommentDeletedDescription": {
+ "description": "Description for the Comment Deleted analytics event"
+ },
+ "analyticsEventReportSubmittedLabel": "Report Submitted",
+ "@analyticsEventReportSubmittedLabel": {
+ "description": "Label for the Report Submitted analytics event"
+ },
+ "analyticsEventReportSubmittedDescription": "Track when a user reports content or other users.",
+ "@analyticsEventReportSubmittedDescription": {
+ "description": "Description for the Report Submitted analytics event"
+ },
+ "analyticsEventHeadlineFilterCreatedLabel": "Headline Filter Creation",
+ "@analyticsEventHeadlineFilterCreatedLabel": {
+ "description": "Label for the Headline Filter Creation analytics event"
+ },
+ "analyticsEventHeadlineFilterCreatedDescription": "Track when a user creates a new custom headline filter.",
+ "@analyticsEventHeadlineFilterCreatedDescription": {
+ "description": "Description for the Headline Filter Creation analytics event"
+ },
+ "analyticsEventHeadlineFilterUpdatedLabel": "Headline Filter Update",
+ "@analyticsEventHeadlineFilterUpdatedLabel": {
+ "description": "Label for the Headline Filter Update analytics event"
+ },
+ "analyticsEventHeadlineFilterUpdatedDescription": "Track when a user modifies an existing headline filter.",
+ "@analyticsEventHeadlineFilterUpdatedDescription": {
+ "description": "Description for the Headline Filter Update analytics event"
+ },
+ "analyticsEventHeadlineFilterUsedLabel": "Headline Filter Usage",
+ "@analyticsEventHeadlineFilterUsedLabel": {
+ "description": "Label for the Headline Filter Usage analytics event"
+ },
+ "analyticsEventHeadlineFilterUsedDescription": "Track when a user applies a saved headline filter.",
+ "@analyticsEventHeadlineFilterUsedDescription": {
+ "description": "Description for the Headline Filter Usage analytics event"
+ },
+ "analyticsEventSourceFilterCreatedLabel": "Source Filter Creation",
+ "@analyticsEventSourceFilterCreatedLabel": {
+ "description": "Label for the Source Filter Creation analytics event"
+ },
+ "analyticsEventSourceFilterCreatedDescription": "Track when a user creates a new source filter.",
+ "@analyticsEventSourceFilterCreatedDescription": {
+ "description": "Description for the Source Filter Creation analytics event"
+ },
+ "analyticsEventSourceFilterUpdatedLabel": "Source Filter Update",
+ "@analyticsEventSourceFilterUpdatedLabel": {
+ "description": "Label for the Source Filter Update analytics event"
+ },
+ "analyticsEventSourceFilterUpdatedDescription": "Track when a user modifies an existing source filter.",
+ "@analyticsEventSourceFilterUpdatedDescription": {
+ "description": "Description for the Source Filter Update analytics event"
+ },
+ "analyticsEventSearchPerformedLabel": "Search Performed",
+ "@analyticsEventSearchPerformedLabel": {
+ "description": "Label for the Search Performed analytics event"
+ },
+ "analyticsEventSearchPerformedDescription": "Track when a user performs a search query.",
+ "@analyticsEventSearchPerformedDescription": {
+ "description": "Description for the Search Performed analytics event"
+ },
+ "analyticsEventAppReviewPromptRespondedLabel": "Review Prompt Response",
+ "@analyticsEventAppReviewPromptRespondedLabel": {
+ "description": "Label for the Review Prompt Response analytics event"
+ },
+ "analyticsEventAppReviewPromptRespondedDescription": "Track user responses to the internal 'Enjoying the app?' prompt.",
+ "@analyticsEventAppReviewPromptRespondedDescription": {
+ "description": "Description for the Review Prompt Response analytics event"
+ },
+ "analyticsEventAppReviewStoreRequestedLabel": "Store Review Request",
+ "@analyticsEventAppReviewStoreRequestedLabel": {
+ "description": "Label for the Store Review Request analytics event"
+ },
+ "analyticsEventAppReviewStoreRequestedDescription": "Track when the native OS store review dialog is requested.",
+ "@analyticsEventAppReviewStoreRequestedDescription": {
+ "description": "Description for the Store Review Request analytics event"
+ },
+ "analyticsEventLimitExceededLabel": "Limit Exceeded",
+ "@analyticsEventLimitExceededLabel": {
+ "description": "Label for the Limit Exceeded analytics event"
+ },
+ "analyticsEventLimitExceededDescription": "Track when a user hits a usage limit (e.g., saved items limit).",
+ "@analyticsEventLimitExceededDescription": {
+ "description": "Description for the Limit Exceeded analytics event"
+ },
+ "analyticsEventLimitExceededCtaClickedLabel": "Limit CTA Click",
+ "@analyticsEventLimitExceededCtaClickedLabel": {
+ "description": "Label for the Limit CTA Click analytics event"
+ },
+ "analyticsEventLimitExceededCtaClickedDescription": "Track clicks on 'Upgrade' or 'Link Account' buttons in limit dialogs.",
+ "@analyticsEventLimitExceededCtaClickedDescription": {
+ "description": "Description for the Limit CTA Click analytics event"
+ },
+ "analyticsEventPaywallPresentedLabel": "Paywall Impression",
+ "@analyticsEventPaywallPresentedLabel": {
+ "description": "Label for the Paywall Impression analytics event"
+ },
+ "analyticsEventPaywallPresentedDescription": "Track when the paywall screen is shown to a user.",
+ "@analyticsEventPaywallPresentedDescription": {
+ "description": "Description for the Paywall Impression analytics event"
+ },
+ "analyticsEventSubscriptionStartedLabel": "Subscription Start",
+ "@analyticsEventSubscriptionStartedLabel": {
+ "description": "Label for the Subscription Start analytics event"
+ },
+ "analyticsEventSubscriptionStartedDescription": "Track when a user successfully starts a new subscription.",
+ "@analyticsEventSubscriptionStartedDescription": {
+ "description": "Description for the Subscription Start analytics event"
+ },
+ "analyticsEventSubscriptionRenewedLabel": "Subscription Renewal",
+ "@analyticsEventSubscriptionRenewedLabel": {
+ "description": "Label for the Subscription Renewal analytics event"
+ },
+ "analyticsEventSubscriptionRenewedDescription": "Track when a subscription is automatically or manually renewed.",
+ "@analyticsEventSubscriptionRenewedDescription": {
+ "description": "Description for the Subscription Renewal analytics event"
+ },
+ "analyticsEventSubscriptionCancelledLabel": "Subscription Cancellation",
+ "@analyticsEventSubscriptionCancelledLabel": {
+ "description": "Label for the Subscription Cancellation analytics event"
+ },
+ "analyticsEventSubscriptionCancelledDescription": "Track when a user cancels their subscription.",
+ "@analyticsEventSubscriptionCancelledDescription": {
+ "description": "Description for the Subscription Cancellation analytics event"
+ },
+ "analyticsEventSubscriptionEndedLabel": "Subscription End",
+ "@analyticsEventSubscriptionEndedLabel": {
+ "description": "Label for the Subscription End analytics event"
+ },
+ "analyticsEventSubscriptionEndedDescription": "Track when a subscription expires or is terminated.",
+ "@analyticsEventSubscriptionEndedDescription": {
+ "description": "Description for the Subscription End analytics event"
+ },
+ "analyticsEventAdImpressionLabel": "Ad Impression",
+ "@analyticsEventAdImpressionLabel": {
+ "description": "Label for the Ad Impression analytics event"
+ },
+ "analyticsEventAdImpressionDescription": "Track when an advertisement is displayed to the user.",
+ "@analyticsEventAdImpressionDescription": {
+ "description": "Description for the Ad Impression analytics event"
+ },
+ "analyticsEventAdClickedLabel": "Ad Click",
+ "@analyticsEventAdClickedLabel": {
+ "description": "Label for the Ad Click analytics event"
+ },
+ "analyticsEventAdClickedDescription": "Track when a user clicks on an advertisement.",
+ "@analyticsEventAdClickedDescription": {
+ "description": "Description for the Ad Click analytics event"
+ },
+ "analyticsEventAdLoadFailedLabel": "Ad Load Failure",
+ "@analyticsEventAdLoadFailedLabel": {
+ "description": "Label for the Ad Load Failure analytics event"
+ },
+ "analyticsEventAdLoadFailedDescription": "Track errors when attempting to load advertisements.",
+ "@analyticsEventAdLoadFailedDescription": {
+ "description": "Description for the Ad Load Failure analytics event"
+ },
+ "analyticsEventAdRewardEarnedLabel": "Ad Reward Earned",
+ "@analyticsEventAdRewardEarnedLabel": {
+ "description": "Label for the Ad Reward Earned analytics event"
+ },
+ "analyticsEventAdRewardEarnedDescription": "Track when a user completes a rewarded ad action.",
+ "@analyticsEventAdRewardEarnedDescription": {
+ "description": "Description for the Ad Reward Earned analytics event"
+ },
+ "analyticsEventThemeChangedLabel": "Theme Change",
+ "@analyticsEventThemeChangedLabel": {
+ "description": "Label for the Theme Change analytics event"
+ },
+ "analyticsEventThemeChangedDescription": "Track when a user changes the application theme.",
+ "@analyticsEventThemeChangedDescription": {
+ "description": "Description for the Theme Change analytics event"
+ },
+ "analyticsEventLanguageChangedLabel": "Language Change",
+ "@analyticsEventLanguageChangedLabel": {
+ "description": "Label for the Language Change analytics event"
+ },
+ "analyticsEventLanguageChangedDescription": "Track when a user changes the application language.",
+ "@analyticsEventLanguageChangedDescription": {
+ "description": "Description for the Language Change analytics event"
+ },
+ "analyticsEventFeedDensityChangedLabel": "Feed Density Change",
+ "@analyticsEventFeedDensityChangedLabel": {
+ "description": "Label for the Feed Density Change analytics event"
+ },
+ "analyticsEventFeedDensityChangedDescription": "Track when a user adjusts the information density of the feed.",
+ "@analyticsEventFeedDensityChangedDescription": {
+ "description": "Description for the Feed Density Change analytics event"
+ },
+ "analyticsEventBrowserChoiceChangedLabel": "Browser Preference Change",
+ "@analyticsEventBrowserChoiceChangedLabel": {
+ "description": "Label for the Browser Preference Change analytics event"
+ },
+ "analyticsEventBrowserChoiceChangedDescription": "Track when a user changes their preferred browser for opening links.",
+ "@analyticsEventBrowserChoiceChangedDescription": {
+ "description": "Description for the Browser Preference Change analytics event"
+ },
+ "analyticsEventSourceFilterUsedLabel": "Source Filter Usage",
+ "@analyticsEventSourceFilterUsedLabel": {
+ "description": "Label for the Source Filter Usage analytics event"
+ },
+ "analyticsEventSourceFilterUsedDescription": "Track when a user applies a saved source filter.",
+ "@analyticsEventSourceFilterUsedDescription": {
+ "description": "Description for the Source Filter Usage analytics event"
}
}
\ No newline at end of file
diff --git a/lib/overview/bloc/overview_bloc.dart b/lib/overview/bloc/overview_bloc.dart
deleted file mode 100644
index f48ff8c2..00000000
--- a/lib/overview/bloc/overview_bloc.dart
+++ /dev/null
@@ -1,86 +0,0 @@
-import 'dart:async';
-
-import 'package:bloc/bloc.dart';
-import 'package:core/core.dart';
-import 'package:data_repository/data_repository.dart';
-import 'package:equatable/equatable.dart';
-import 'package:stream_transform/stream_transform.dart';
-
-part 'overview_event.dart';
-part 'overview_state.dart';
-
-/// A BLoC to manage the state of the dashboard overview.
-class OverviewBloc extends Bloc {
- /// {@macro overview_bloc}
- OverviewBloc({
- required DataRepository dashboardSummaryRepository,
- required DataRepository headlinesRepository,
- required DataRepository topicsRepository,
- required DataRepository sourcesRepository,
- }) : _dashboardSummaryRepository = dashboardSummaryRepository,
- _headlinesRepository = headlinesRepository,
- super(const OverviewState()) {
- on(_onOverviewSummaryRequested);
- on<_OverviewEntityUpdated>(_onOverviewEntityUpdated);
-
- _entityUpdatedSubscription = headlinesRepository.entityUpdated
- .merge(topicsRepository.entityUpdated)
- .merge(sourcesRepository.entityUpdated)
- .listen((_) => add(const _OverviewEntityUpdated()));
- }
-
- final DataRepository _dashboardSummaryRepository;
- final DataRepository _headlinesRepository;
- late final StreamSubscription _entityUpdatedSubscription;
-
- @override
- Future close() {
- _entityUpdatedSubscription.cancel();
- return super.close();
- }
-
- void _onOverviewEntityUpdated(
- _OverviewEntityUpdated event,
- Emitter emit,
- ) {
- add(OverviewSummaryRequested());
- }
-
- Future _onOverviewSummaryRequested(
- OverviewSummaryRequested event,
- Emitter emit,
- ) async {
- emit(state.copyWith(status: OverviewStatus.loading));
- try {
- // Fetch summary and recent headlines concurrently
- final [summaryResponse, recentHeadlinesResponse] = await Future.wait([
- _dashboardSummaryRepository.read(id: kDashboardSummaryId),
- _headlinesRepository.readAll(
- pagination: const PaginationOptions(limit: 5),
- sort: const [SortOption('updatedAt', SortOrder.desc)],
- filter: {'status': ContentStatus.active.name},
- ),
- ]);
-
- final summary = summaryResponse as DashboardSummary;
- final recentHeadlines =
- (recentHeadlinesResponse as PaginatedResponse).items;
- emit(
- state.copyWith(
- status: OverviewStatus.success,
- summary: summary,
- recentHeadlines: recentHeadlines,
- ),
- );
- } on HttpException catch (e) {
- emit(state.copyWith(status: OverviewStatus.failure, exception: e));
- } catch (e) {
- emit(
- state.copyWith(
- status: OverviewStatus.failure,
- exception: UnknownException('An unknown error occurred: $e'),
- ),
- );
- }
- }
-}
diff --git a/lib/overview/bloc/overview_event.dart b/lib/overview/bloc/overview_event.dart
deleted file mode 100644
index a81664f8..00000000
--- a/lib/overview/bloc/overview_event.dart
+++ /dev/null
@@ -1,17 +0,0 @@
-part of 'overview_bloc.dart';
-
-/// Base class for dashboard overview events.
-sealed class OverviewEvent extends Equatable {
- const OverviewEvent();
-
- @override
- List