Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3fdbbd2
build(dependencies): update core dependency to latest commit
fulleni Dec 20, 2025
36818ca
feat(l10n): add analytics related strings for English and Arabic
fulleni Dec 20, 2025
2bb4dcd
build(l10n): sync
fulleni Dec 20, 2025
e121d04
feat(app_configuration): add analytics config form widget
fulleni Dec 20, 2025
9374afd
feat(app_configuration): add analytics configuration tab
fulleni Dec 20, 2025
7c655a7
chore: feature overhaul
fulleni Dec 20, 2025
de3636c
chore: misc
fulleni Dec 20, 2025
d3598e2
docs(l10n): add analytics event labels and descriptions
fulleni Dec 20, 2025
c52eccd
build(l10n): sync
fulleni Dec 20, 2025
66c7226
feat(app_configuration): add analytics configuration to features tab
fulleni Dec 20, 2025
6e7034c
feat(analytics): enhance event configuration with labels and descript…
fulleni Dec 20, 2025
a4714de
fix(analytics): handle null values and update event descriptions
fulleni Dec 20, 2025
2311f44
refactor(features-tab): remove redundnt analytics configuration
fulleni Dec 20, 2025
35ce718
feat(app_configuration): add feed config form widget
fulleni Dec 20, 2025
d1e615b
refactor(app_configuration): simplify features configuration tab
fulleni Dec 20, 2025
a616919
feat(analytics): conditionally render config sections based on analyt…
fulleni Dec 20, 2025
d127215
feat(push-notification): conditionally render settings sections based…
fulleni Dec 20, 2025
7711cfe
style: format
fulleni Dec 20, 2025
4f7d5f3
refactor(README): streamline remote control & app configuration details
fulleni Dec 20, 2025
0e1aa40
refactor(analytics): improve event info retrieval
fulleni Dec 20, 2025
b3a86d2
refactor(app_configuration): use enum for ExpansionTile state management
fulleni Dec 20, 2025
e0707c3
style: format
fulleni Dec 20, 2025
4c62e71
build: upgrade pinput package from 5.0.1 to 6.0.1
fulleni Dec 20, 2025
69ef8ab
chore(dependencies): revert pinput version to 6.0.0
fulleni Dec 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 8 additions & 14 deletions lib/app/view/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -40,7 +40,6 @@ class App extends StatelessWidget {
required DataRepository<UserContentPreferences>
userContentPreferencesRepository,
required DataRepository<RemoteConfig> remoteConfigRepository,
required DataRepository<DashboardSummary> dashboardSummaryRepository,
required DataRepository<Country> countriesRepository,
required DataRepository<Language> languagesRepository,
required DataRepository<User> usersRepository,
Expand All @@ -59,7 +58,6 @@ class App extends StatelessWidget {
_userContentPreferencesRepository = userContentPreferencesRepository,
_remoteConfigRepository = remoteConfigRepository,
_kvStorageService = storageService,
_dashboardSummaryRepository = dashboardSummaryRepository,
_countriesRepository = countriesRepository,
_languagesRepository = languagesRepository,
_usersRepository = usersRepository,
Expand All @@ -77,7 +75,6 @@ class App extends StatelessWidget {
final DataRepository<UserContentPreferences>
_userContentPreferencesRepository;
final DataRepository<RemoteConfig> _remoteConfigRepository;
final DataRepository<DashboardSummary> _dashboardSummaryRepository;
final DataRepository<Country> _countriesRepository;
final DataRepository<Language> _languagesRepository;
final DataRepository<User> _usersRepository;
Expand All @@ -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),
Expand Down Expand Up @@ -163,15 +159,13 @@ class App extends StatelessWidget {
pendingDeletionsService: context.read<PendingDeletionsService>(),
),
),
BlocProvider(
create: (context) => OverviewBloc(
dashboardSummaryRepository: context
.read<DataRepository<DashboardSummary>>(),
headlinesRepository: context.read<DataRepository<Headline>>(),
topicsRepository: context.read<DataRepository<Topic>>(),
sourcesRepository: context.read<DataRepository<Source>>(),
),
),
// BlocProvider(
// create: (context) => OverviewBloc(
// headlinesRepository: context.read<DataRepository<Headline>>(),
// topicsRepository: context.read<DataRepository<Topic>>(),
// sourcesRepository: context.read<DataRepository<Source>>(),
// ),
// ),
// The UserFilterBloc is provided here to be available for both the
// UserManagementBloc and the UI components.
BlocProvider(create: (_) => UserFilterBloc()),
Expand Down
204 changes: 67 additions & 137 deletions lib/app_configuration/view/tabs/features_configuration_tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ 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_config_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/navigation_ad_settings_form.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/push_notification_settings_form.dart';
Expand Down Expand Up @@ -49,27 +51,6 @@ class _FeaturesConfigurationTabState extends State<FeaturesConfigurationTab> {
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;
Expand Down Expand Up @@ -114,21 +95,23 @@ class _FeaturesConfigurationTabState extends State<FeaturesConfigurationTab> {
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,
),
],
],
);
},
Expand Down Expand Up @@ -178,11 +161,54 @@ class _FeaturesConfigurationTabState extends State<FeaturesConfigurationTab> {
),
const SizedBox(height: AppSpacing.lg),

// Feed
// Analytics
ValueListenableBuilder<int?>(
valueListenable: _expandedTileIndex,
builder: (context, expandedIndex, child) {
const tileIndex = 2;
return ExpansionTile(
leading: Icon(
Icons.analytics_outlined,
color: Theme.of(context).colorScheme.onSurface.withOpacity(
0.7,
),
),
key: ValueKey('analyticsTile_$expandedIndex'),
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) {
_expandedTileIndex.value = isExpanded ? tileIndex : null;
},
initiallyExpanded: expandedIndex == tileIndex,
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<int?>(
valueListenable: _expandedTileIndex,
builder: (context, expandedIndex, child) {
const tileIndex = 3;
return ExpansionTile(
leading: Icon(
Icons.dynamic_feed_outlined,
Expand Down Expand Up @@ -211,105 +237,9 @@ class _FeaturesConfigurationTabState extends State<FeaturesConfigurationTab> {
),
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<FeedItemClickBehavior>(
segments: FeedItemClickBehavior.values
.where(
(b) => b != FeedItemClickBehavior.defaultBehavior,
)
.map(
(behavior) =>
ButtonSegment<FeedItemClickBehavior>(
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,
),
],
);
Expand All @@ -321,7 +251,7 @@ class _FeaturesConfigurationTabState extends State<FeaturesConfigurationTab> {
ValueListenableBuilder<int?>(
valueListenable: _expandedTileIndex,
builder: (context, expandedIndex, child) {
const tileIndex = 3;
const tileIndex = 4;
return ExpansionTile(
leading: Icon(
Icons.groups_outlined,
Expand Down
Loading
Loading