Skip to content

Commit b2d3d03

Browse files
authored
Merge pull request #127 from flutter-news-app-full-source-code/feat/push-notification
Feat/push notification
2 parents b558175 + 0dc1be2 commit b2d3d03

38 files changed

+1790
-255
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Dynamically control the mobile app's behavior and operational state directly fro
6262
- **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.
6363
- **Dynamic In-App Content:** Remotely manage the visibility and behavior of in-feed promotional prompts and user engagement elements.
6464
- **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.
65+
- **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.
6566
> **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.
6667
6768
</details>

lib/app_configuration/view/app_configuration_page.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuratio
44
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/advertisements_configuration_tab.dart';
55
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/feed_configuration_tab.dart';
66
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/general_configuration_tab.dart';
7+
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/push_notification_settings_form.dart';
78
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
89
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/about_icon.dart';
910
import 'package:ui_kit/ui_kit.dart';
@@ -30,7 +31,7 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
3031
@override
3132
void initState() {
3233
super.initState();
33-
_tabController = TabController(length: 3, vsync: this);
34+
_tabController = TabController(length: 4, vsync: this);
3435
context.read<AppConfigurationBloc>().add(const AppConfigurationLoaded());
3536
}
3637

@@ -68,6 +69,7 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
6869
Tab(text: l10n.generalTab),
6970
Tab(text: l10n.feedTab),
7071
Tab(text: l10n.advertisementsTab),
72+
Tab(text: l10n.notificationsTab),
7173
],
7274
),
7375
),
@@ -156,6 +158,14 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
156158
);
157159
},
158160
),
161+
PushNotificationSettingsForm(
162+
remoteConfig: remoteConfig,
163+
onConfigChanged: (newConfig) {
164+
context.read<AppConfigurationBloc>().add(
165+
AppConfigurationFieldChanged(remoteConfig: newConfig),
166+
);
167+
},
168+
),
159169
],
160170
);
161171
}

lib/app_configuration/view/tabs/feed_configuration_tab.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'package:core/core.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/feed_decorator_form.dart';
4-
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/saved_feed_filters_limit_form.dart';
4+
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/saved_filter_limits_section.dart';
55
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/user_preference_limits_form.dart';
66
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
77
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/feed_decorator_type_l10n.dart';
@@ -93,7 +93,7 @@ class _FeedConfigurationTabState extends State<FeedConfigurationTab> {
9393
const tileIndex = 1;
9494
return ExpansionTile(
9595
key: ValueKey('savedFeedFilterLimitsTile_$expandedIndex'),
96-
title: Text(l10n.savedFeedFiltersLimitLabel),
96+
title: Text(l10n.savedFeedFilterLimitsTitle),
9797
childrenPadding: const EdgeInsetsDirectional.only(
9898
start: AppSpacing.lg,
9999
top: AppSpacing.md,
@@ -114,7 +114,7 @@ class _FeedConfigurationTabState extends State<FeedConfigurationTab> {
114114
),
115115
),
116116
const SizedBox(height: AppSpacing.lg),
117-
SavedFeedFiltersLimitForm(
117+
SavedFilterLimitsSection(
118118
remoteConfig: widget.remoteConfig,
119119
onConfigChanged: widget.onConfigChanged,
120120
),
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import 'package:core/core.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
4+
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
5+
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/push_notification_provider_l10n.dart';
6+
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/push_notification_subscription_delivery_type_l10n.dart';
7+
import 'package:ui_kit/ui_kit.dart';
8+
9+
/// {@template push_notification_settings_form}
10+
/// A form widget for configuring push notification settings.
11+
/// {@endtemplate}
12+
class PushNotificationSettingsForm extends StatelessWidget {
13+
/// {@macro push_notification_settings_form}
14+
const PushNotificationSettingsForm({
15+
required this.remoteConfig,
16+
required this.onConfigChanged,
17+
super.key,
18+
});
19+
20+
/// The current [RemoteConfig] object.
21+
final RemoteConfig remoteConfig;
22+
23+
/// Callback to notify parent of changes to the [RemoteConfig].
24+
final ValueChanged<RemoteConfig> onConfigChanged;
25+
26+
@override
27+
Widget build(BuildContext context) {
28+
final l10n = AppLocalizationsX(context).l10n;
29+
final pushConfig = remoteConfig.pushNotificationConfig;
30+
31+
return SingleChildScrollView(
32+
padding: const EdgeInsets.all(AppSpacing.lg),
33+
child: Column(
34+
crossAxisAlignment: CrossAxisAlignment.start,
35+
children: [
36+
SwitchListTile(
37+
title: Text(l10n.pushNotificationSystemStatusTitle),
38+
subtitle: Text(l10n.pushNotificationSystemStatusDescription),
39+
value: pushConfig.enabled,
40+
onChanged: (value) {
41+
onConfigChanged(
42+
remoteConfig.copyWith(
43+
pushNotificationConfig: pushConfig.copyWith(enabled: value),
44+
),
45+
);
46+
},
47+
),
48+
const SizedBox(height: AppSpacing.lg),
49+
_buildPrimaryProviderSection(context, l10n, pushConfig),
50+
const SizedBox(height: AppSpacing.lg),
51+
_buildDeliveryTypesSection(context, l10n, pushConfig),
52+
],
53+
),
54+
);
55+
}
56+
57+
Widget _buildPrimaryProviderSection(
58+
BuildContext context,
59+
AppLocalizations l10n,
60+
PushNotificationConfig pushConfig,
61+
) {
62+
return ExpansionTile(
63+
title: Text(l10n.pushNotificationPrimaryProviderTitle),
64+
childrenPadding: const EdgeInsetsDirectional.only(
65+
start: AppSpacing.lg,
66+
top: AppSpacing.md,
67+
bottom: AppSpacing.md,
68+
),
69+
expandedCrossAxisAlignment: CrossAxisAlignment.start,
70+
children: [
71+
Text(
72+
l10n.pushNotificationPrimaryProviderDescription,
73+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
74+
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
75+
),
76+
),
77+
const SizedBox(height: AppSpacing.lg),
78+
Align(
79+
alignment: AlignmentDirectional.centerStart,
80+
child: SegmentedButton<PushNotificationProvider>(
81+
segments: PushNotificationProvider.values
82+
.map(
83+
(provider) => ButtonSegment<PushNotificationProvider>(
84+
value: provider,
85+
label: Text(provider.l10n(context)),
86+
),
87+
)
88+
.toList(),
89+
selected: {pushConfig.primaryProvider},
90+
onSelectionChanged: (newSelection) {
91+
onConfigChanged(
92+
remoteConfig.copyWith(
93+
pushNotificationConfig: pushConfig.copyWith(
94+
primaryProvider: newSelection.first,
95+
),
96+
),
97+
);
98+
},
99+
),
100+
),
101+
],
102+
);
103+
}
104+
105+
Widget _buildDeliveryTypesSection(
106+
BuildContext context,
107+
AppLocalizations l10n,
108+
PushNotificationConfig pushConfig,
109+
) {
110+
return ExpansionTile(
111+
title: Text(l10n.pushNotificationDeliveryTypesTitle),
112+
childrenPadding: const EdgeInsetsDirectional.only(
113+
start: AppSpacing.lg,
114+
top: AppSpacing.md,
115+
bottom: AppSpacing.md,
116+
),
117+
expandedCrossAxisAlignment: CrossAxisAlignment.start,
118+
children: [
119+
Text(
120+
l10n.pushNotificationDeliveryTypesDescription,
121+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
122+
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
123+
),
124+
),
125+
const SizedBox(height: AppSpacing.lg),
126+
Column(
127+
children: PushNotificationSubscriptionDeliveryType.values
128+
.map(
129+
(type) => SwitchListTile(
130+
title: Text(type.l10n(context)),
131+
value: pushConfig.deliveryConfigs[type] ?? false,
132+
onChanged: (value) {
133+
final newDeliveryConfigs =
134+
Map<
135+
PushNotificationSubscriptionDeliveryType,
136+
bool
137+
>.from(
138+
pushConfig.deliveryConfigs,
139+
);
140+
newDeliveryConfigs[type] = value;
141+
onConfigChanged(
142+
remoteConfig.copyWith(
143+
pushNotificationConfig: pushConfig.copyWith(
144+
deliveryConfigs: newDeliveryConfigs,
145+
),
146+
),
147+
);
148+
},
149+
),
150+
)
151+
.toList(),
152+
),
153+
],
154+
);
155+
}
156+
}

0 commit comments

Comments
 (0)