Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
497cae1
build(deps): update core and http-client dependencies
fulleni Nov 12, 2025
d49a086
feat(l10n): add localization for new filter limit configurations
fulleni Nov 12, 2025
286e8c2
feat(l10n): add Arabic translations for filter limits and notificatio…
fulleni Nov 12, 2025
1d794a6
feat(app_config): create reusable saved filter limits form
fulleni Nov 12, 2025
bc848ee
refactor(app_config): update user preference form for new data model
fulleni Nov 12, 2025
f73ea63
refactor(app_config): repurpose saved_feed_filters_limit_form
fulleni Nov 12, 2025
f851774
refactor(app_config): update feed tab to use new filter limit container
fulleni Nov 12, 2025
d631b4b
build(l10n): generation
fulleni Nov 12, 2025
1b2e012
feat(l10n): add localization extension for PushNotificationSubscripti…
fulleni Nov 12, 2025
8fb0d39
refactor(app_config): rename saved filter limit widgets for clarity
fulleni Nov 12, 2025
0e42c68
feat(create_headline): add isBreaking changed event
fulleni Nov 12, 2025
bce2780
feat(create_headline): add isBreaking to state and form validation
fulleni Nov 12, 2025
43165f0
feat(create_headline): handle isBreaking event and include in headlin…
fulleni Nov 12, 2025
a024606
feat(create_headline): add isBreaking toggle and confirmation dialog
fulleni Nov 12, 2025
d2d0534
feat(edit_headline): add isBreaking changed event
fulleni Nov 12, 2025
8d49993
feat(edit_headline): add isBreaking to state
fulleni Nov 12, 2025
d3ab226
feat(edit_headline): handle isBreaking event and include in headline …
fulleni Nov 12, 2025
513c770
feat(edit_headline): add isBreaking toggle to edit headline page
fulleni Nov 12, 2025
f881f53
feat(headlines): display breaking news icon in data table
fulleni Nov 12, 2025
10b9ad8
feat(l10n): add Arabic and English translations for breaking news fea…
fulleni Nov 12, 2025
39bfded
build(l10n): generation
fulleni Nov 12, 2025
fae10f9
fix(content_management): check context.mounted before popping routes
fulleni Nov 12, 2025
26a257c
refactor(headlines): improve breaking news icon alignment and RTL sup…
fulleni Nov 12, 2025
3948b14
feat(headlines_filter): add isBreaking to filter state
fulleni Nov 12, 2025
970c84b
feat(headlines_filter): add breaking news filter event
fulleni Nov 12, 2025
44cd479
feat(headlines_filter): handle breaking news filter state
fulleni Nov 12, 2025
b0f6be1
feat(content_management): update headlines filter map with isBreaking
fulleni Nov 12, 2025
872fbe5
feat(filter_dialog): add isBreaking to dialog state
fulleni Nov 12, 2025
0bdced6
feat(filter_dialog): add breaking news changed event
fulleni Nov 12, 2025
4b422b6
feat(filter_dialog): handle breaking news dialog state
fulleni Nov 12, 2025
9f2f4f3
feat(filter_dialog): add breaking news filter UI
fulleni Nov 12, 2025
2b61987
feat(l10n): add breaking news filter translations
fulleni Nov 12, 2025
e6ed452
build(l10n): generation
fulleni Nov 12, 2025
27fe07b
style: format
fulleni Nov 12, 2025
58fbef9
feat(headlines_filter): introduce BreakingNewsFilterStatus enum
fulleni Nov 12, 2025
aaeab83
Revert "feat(headlines_filter): introduce BreakingNewsFilterStatus enum"
fulleni Nov 12, 2025
580ec07
feat(content): introduce BreakingNewsFilterStatus enum
fulleni Nov 13, 2025
ec19832
refactor(content): update HeadlinesFilterState to use enum
fulleni Nov 13, 2025
980558f
refactor(content): update HeadlinesFilterEvent to use enum
fulleni Nov 13, 2025
c6f2218
refactor(content): update FilterDialogEvent to use enum
fulleni Nov 13, 2025
5f4acf1
refactor(content_management): improve FilterDialogBreakingNewsChanged…
fulleni Nov 13, 2025
a85d44b
fix(content_management): improve breaking news filter chip selection
fulleni Nov 13, 2025
243c695
style(content_management): update filter dialog code formatting
fulleni Nov 13, 2025
4a57839
refactor(content_management): update headlines filter breaking news s…
fulleni Nov 13, 2025
07b4527
feat(content_management): enhance breaking news filtering with enum s…
fulleni Nov 13, 2025
7b55c37
fix(content_management): trigger headlines load when breaking news to…
fulleni Nov 13, 2025
a441605
refactor(filter_dialog): improve breaking news filter UI and accessib…
fulleni Nov 13, 2025
351a182
fix(content): prevent text auto-selection in filter dialog
fulleni Nov 13, 2025
595893b
feat(l10n): add push notification settings translations
fulleni Nov 13, 2025
7a332b6
build(l10n): generate
fulleni Nov 13, 2025
192ea6a
feat(app_configuration): add push notification settings form
fulleni Nov 13, 2025
b32a5c3
feat(app_configuration): add push notification settings form
fulleni Nov 13, 2025
7958d2f
feat(l10n): add localization extension for PushNotificationProvider
fulleni Nov 13, 2025
69f3e30
refactor(app_configuration): simplify push notification settings form
fulleni Nov 13, 2025
18c8ef8
fix(l10n): update push notification settings descriptions and titles
fulleni Nov 13, 2025
c5e2b5a
build(l10n): generate
fulleni Nov 13, 2025
807c671
feat(l10n): add push notification provider configuration notes
fulleni Nov 13, 2025
971cc44
refactor(push-notification): update provider configuration descriptions
fulleni Nov 13, 2025
0dc1be2
docs(README): add Global Notification Control feature description
fulleni Nov 13, 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Dynamically control the mobile app's behavior and operational state directly fro
- **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.
- **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.

</details>
Expand Down
12 changes: 11 additions & 1 deletion lib/app_configuration/view/app_configuration_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuratio
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/advertisements_configuration_tab.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/feed_configuration_tab.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/general_configuration_tab.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/widgets/about_icon.dart';
import 'package:ui_kit/ui_kit.dart';
Expand All @@ -30,7 +31,7 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
_tabController = TabController(length: 4, vsync: this);
context.read<AppConfigurationBloc>().add(const AppConfigurationLoaded());
}

Expand Down Expand Up @@ -68,6 +69,7 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
Tab(text: l10n.generalTab),
Tab(text: l10n.feedTab),
Tab(text: l10n.advertisementsTab),
Tab(text: l10n.notificationsTab),
],
),
),
Expand Down Expand Up @@ -156,6 +158,14 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
);
},
),
PushNotificationSettingsForm(
remoteConfig: remoteConfig,
onConfigChanged: (newConfig) {
context.read<AppConfigurationBloc>().add(
AppConfigurationFieldChanged(remoteConfig: newConfig),
);
},
),
],
);
}
Expand Down
6 changes: 3 additions & 3 deletions lib/app_configuration/view/tabs/feed_configuration_tab.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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/app_configuration/widgets/saved_feed_filters_limit_form.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/saved_filter_limits_section.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/user_preference_limits_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';
Expand Down Expand Up @@ -93,7 +93,7 @@ class _FeedConfigurationTabState extends State<FeedConfigurationTab> {
const tileIndex = 1;
return ExpansionTile(
key: ValueKey('savedFeedFilterLimitsTile_$expandedIndex'),
title: Text(l10n.savedFeedFiltersLimitLabel),
title: Text(l10n.savedFeedFilterLimitsTitle),
childrenPadding: const EdgeInsetsDirectional.only(
start: AppSpacing.lg,
top: AppSpacing.md,
Expand All @@ -114,7 +114,7 @@ class _FeedConfigurationTabState extends State<FeedConfigurationTab> {
),
),
const SizedBox(height: AppSpacing.lg),
SavedFeedFiltersLimitForm(
SavedFilterLimitsSection(
remoteConfig: widget.remoteConfig,
onConfigChanged: widget.onConfigChanged,
),
Expand Down
156 changes: 156 additions & 0 deletions lib/app_configuration/widgets/push_notification_settings_form.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
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:flutter_news_app_web_dashboard_full_source_code/shared/extensions/push_notification_provider_l10n.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/push_notification_subscription_delivery_type_l10n.dart';
import 'package:ui_kit/ui_kit.dart';

/// {@template push_notification_settings_form}
/// A form widget for configuring push notification settings.
/// {@endtemplate}
class PushNotificationSettingsForm extends StatelessWidget {
/// {@macro push_notification_settings_form}
const PushNotificationSettingsForm({
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<RemoteConfig> onConfigChanged;

@override
Widget build(BuildContext context) {
final l10n = AppLocalizationsX(context).l10n;
final pushConfig = remoteConfig.pushNotificationConfig;

return SingleChildScrollView(
padding: const EdgeInsets.all(AppSpacing.lg),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SwitchListTile(
title: Text(l10n.pushNotificationSystemStatusTitle),
subtitle: Text(l10n.pushNotificationSystemStatusDescription),
value: pushConfig.enabled,
onChanged: (value) {
onConfigChanged(
remoteConfig.copyWith(
pushNotificationConfig: pushConfig.copyWith(enabled: value),
),
);
},
),
const SizedBox(height: AppSpacing.lg),
_buildPrimaryProviderSection(context, l10n, pushConfig),
const SizedBox(height: AppSpacing.lg),
_buildDeliveryTypesSection(context, l10n, pushConfig),
],
),
);
}

Widget _buildPrimaryProviderSection(
BuildContext context,
AppLocalizations l10n,
PushNotificationConfig pushConfig,
) {
return ExpansionTile(
title: Text(l10n.pushNotificationPrimaryProviderTitle),
childrenPadding: const EdgeInsetsDirectional.only(
start: AppSpacing.lg,
top: AppSpacing.md,
bottom: AppSpacing.md,
),
expandedCrossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.pushNotificationPrimaryProviderDescription,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
),
),
const SizedBox(height: AppSpacing.lg),
Align(
alignment: AlignmentDirectional.centerStart,
child: SegmentedButton<PushNotificationProvider>(
segments: PushNotificationProvider.values
.map(
(provider) => ButtonSegment<PushNotificationProvider>(
value: provider,
label: Text(provider.l10n(context)),
),
)
.toList(),
selected: {pushConfig.primaryProvider},
onSelectionChanged: (newSelection) {
onConfigChanged(
remoteConfig.copyWith(
pushNotificationConfig: pushConfig.copyWith(
primaryProvider: newSelection.first,
),
),
);
},
),
),
],
);
}

Widget _buildDeliveryTypesSection(
BuildContext context,
AppLocalizations l10n,
PushNotificationConfig pushConfig,
) {
return ExpansionTile(
title: Text(l10n.pushNotificationDeliveryTypesTitle),
childrenPadding: const EdgeInsetsDirectional.only(
start: AppSpacing.lg,
top: AppSpacing.md,
bottom: AppSpacing.md,
),
expandedCrossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.pushNotificationDeliveryTypesDescription,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
),
),
const SizedBox(height: AppSpacing.lg),
Column(
children: PushNotificationSubscriptionDeliveryType.values
.map(
(type) => SwitchListTile(
title: Text(type.l10n(context)),
value: pushConfig.deliveryConfigs[type] ?? false,
onChanged: (value) {
final newDeliveryConfigs =
Map<
PushNotificationSubscriptionDeliveryType,
bool
>.from(
pushConfig.deliveryConfigs,
);
newDeliveryConfigs[type] = value;
onConfigChanged(
remoteConfig.copyWith(
pushNotificationConfig: pushConfig.copyWith(
deliveryConfigs: newDeliveryConfigs,
),
),
);
},
),
)
.toList(),
),
],
);
}
}
Loading
Loading