Skip to content

Commit d9450b5

Browse files
committed
feat(app_configuration): add ad config form widget
- Implement AdConfigForm StatefulWidget for configuring ad settings - Use SegmentedButton to select user role - Conditionally render input fields based on selected user role - Manage text controllers for ad frequency, placement interval, and interstitial ads - Update remote config on field changes - Handle initialization and updates of controller values - Dispose of text controllers
1 parent 69df48c commit d9450b5

File tree

1 file changed

+362
-0
lines changed

1 file changed

+362
-0
lines changed
Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
import 'package:core/core.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/app_config_form_fields.dart';
4+
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
5+
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
6+
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/app_user_role_l10n.dart';
7+
import 'package:ui_kit/ui_kit.dart';
8+
9+
/// {@template ad_config_form}
10+
/// A form widget for configuring ad settings based on user role.
11+
///
12+
/// This widget uses a [SegmentedButton] to allow selection of an [AppUserRole]
13+
/// and then conditionally renders the relevant input fields for that role.
14+
/// {@endtemplate}
15+
class AdConfigForm extends StatefulWidget {
16+
/// {@macro ad_config_form}
17+
const AdConfigForm({
18+
required this.remoteConfig,
19+
required this.onConfigChanged,
20+
super.key,
21+
});
22+
23+
/// The current [RemoteConfig] object.
24+
final RemoteConfig remoteConfig;
25+
26+
/// Callback to notify parent of changes to the [RemoteConfig].
27+
final ValueChanged<RemoteConfig> onConfigChanged;
28+
29+
@override
30+
State<AdConfigForm> createState() => _AdConfigFormState();
31+
}
32+
33+
class _AdConfigFormState extends State<AdConfigForm> {
34+
AppUserRole _selectedUserRole = AppUserRole.guestUser;
35+
late final Map<AppUserRole, TextEditingController> _adFrequencyControllers;
36+
late final Map<AppUserRole, TextEditingController> _adPlacementIntervalControllers;
37+
late final Map<AppUserRole, TextEditingController>
38+
_articlesToReadBeforeShowingInterstitialAdsControllers;
39+
40+
@override
41+
void initState() {
42+
super.initState();
43+
_initializeControllers();
44+
}
45+
46+
@override
47+
void didUpdateWidget(covariant AdConfigForm oldWidget) {
48+
super.didUpdateWidget(oldWidget);
49+
if (widget.remoteConfig.adConfig != oldWidget.remoteConfig.adConfig) {
50+
_updateControllers();
51+
}
52+
}
53+
54+
void _initializeControllers() {
55+
final adConfig = widget.remoteConfig.adConfig;
56+
_adFrequencyControllers = {
57+
for (final role in AppUserRole.values)
58+
role: TextEditingController(
59+
text: _getAdFrequency(adConfig, role).toString(),
60+
)..selection = TextSelection.collapsed(
61+
offset: _getAdFrequency(adConfig, role).toString().length,
62+
),
63+
};
64+
_adPlacementIntervalControllers = {
65+
for (final role in AppUserRole.values)
66+
role: TextEditingController(
67+
text: _getAdPlacementInterval(adConfig, role).toString(),
68+
)..selection = TextSelection.collapsed(
69+
offset: _getAdPlacementInterval(adConfig, role).toString().length,
70+
),
71+
};
72+
_articlesToReadBeforeShowingInterstitialAdsControllers = {
73+
for (final role in AppUserRole.values)
74+
role: TextEditingController(
75+
text: _getArticlesBeforeInterstitial(adConfig, role).toString(),
76+
)..selection = TextSelection.collapsed(
77+
offset: _getArticlesBeforeInterstitial(adConfig, role)
78+
.toString()
79+
.length,
80+
),
81+
};
82+
}
83+
84+
void _updateControllers() {
85+
final adConfig = widget.remoteConfig.adConfig;
86+
for (final role in AppUserRole.values) {
87+
_adFrequencyControllers[role]?.text =
88+
_getAdFrequency(adConfig, role).toString();
89+
_adFrequencyControllers[role]?.selection = TextSelection.collapsed(
90+
offset: _adFrequencyControllers[role]!.text.length,
91+
);
92+
_adPlacementIntervalControllers[role]?.text =
93+
_getAdPlacementInterval(adConfig, role).toString();
94+
_adPlacementIntervalControllers[role]?.selection = TextSelection.collapsed(
95+
offset: _adPlacementIntervalControllers[role]!.text.length,
96+
);
97+
_articlesToReadBeforeShowingInterstitialAdsControllers[role]?.text =
98+
_getArticlesBeforeInterstitial(adConfig, role).toString();
99+
_articlesToReadBeforeShowingInterstitialAdsControllers[role]?.selection =
100+
TextSelection.collapsed(
101+
offset: _articlesToReadBeforeShowingInterstitialAdsControllers[role]!
102+
.text
103+
.length,
104+
);
105+
}
106+
}
107+
108+
@override
109+
void dispose() {
110+
for (final controller in _adFrequencyControllers.values) {
111+
controller.dispose();
112+
}
113+
for (final controller in _adPlacementIntervalControllers.values) {
114+
controller.dispose();
115+
}
116+
for (final controller
117+
in _articlesToReadBeforeShowingInterstitialAdsControllers.values) {
118+
controller.dispose();
119+
}
120+
super.dispose();
121+
}
122+
123+
@override
124+
Widget build(BuildContext context) {
125+
final adConfig = widget.remoteConfig.adConfig;
126+
final l10n = AppLocalizationsX(context).l10n;
127+
128+
return Column(
129+
crossAxisAlignment: CrossAxisAlignment.start,
130+
children: [
131+
Text(
132+
l10n.adSettingsDescription,
133+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
134+
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
135+
),
136+
),
137+
const SizedBox(height: AppSpacing.lg),
138+
Center(
139+
child: SegmentedButton<AppUserRole>(
140+
segments: AppUserRole.values
141+
.map(
142+
(role) => ButtonSegment<AppUserRole>(
143+
value: role,
144+
label: Text(role.l10n(context)),
145+
),
146+
)
147+
.toList(),
148+
selected: {_selectedUserRole},
149+
onSelectionChanged: (newSelection) {
150+
setState(() {
151+
_selectedUserRole = newSelection.first;
152+
});
153+
},
154+
),
155+
),
156+
const SizedBox(height: AppSpacing.lg),
157+
// Conditionally render fields based on selected user role
158+
if (_selectedUserRole == AppUserRole.guestUser)
159+
_buildRoleSpecificFields(
160+
context,
161+
l10n,
162+
AppUserRole.guestUser,
163+
adConfig,
164+
),
165+
if (_selectedUserRole == AppUserRole.standardUser)
166+
_buildRoleSpecificFields(
167+
context,
168+
l10n,
169+
AppUserRole.standardUser,
170+
adConfig,
171+
),
172+
if (_selectedUserRole == AppUserRole.premiumUser)
173+
_buildRoleSpecificFields(
174+
context,
175+
l10n,
176+
AppUserRole.premiumUser,
177+
adConfig,
178+
),
179+
],
180+
);
181+
}
182+
183+
Widget _buildRoleSpecificFields(
184+
BuildContext context,
185+
AppLocalizations l10n,
186+
AppUserRole role,
187+
AdConfig config,
188+
) {
189+
return Column(
190+
children: [
191+
AppConfigIntField(
192+
label: l10n.adFrequencyLabel,
193+
description: l10n.adFrequencyDescription,
194+
value: _getAdFrequency(config, role),
195+
onChanged: (value) {
196+
widget.onConfigChanged(
197+
widget.remoteConfig.copyWith(
198+
adConfig: _updateAdFrequency(config, value, role),
199+
),
200+
);
201+
},
202+
controller: _adFrequencyControllers[role],
203+
),
204+
AppConfigIntField(
205+
label: l10n.adPlacementIntervalLabel,
206+
description: l10n.adPlacementIntervalDescription,
207+
value: _getAdPlacementInterval(config, role),
208+
onChanged: (value) {
209+
widget.onConfigChanged(
210+
widget.remoteConfig.copyWith(
211+
adConfig: _updateAdPlacementInterval(config, value, role),
212+
),
213+
);
214+
},
215+
controller: _adPlacementIntervalControllers[role],
216+
),
217+
AppConfigIntField(
218+
label: l10n.articlesBeforeInterstitialAdsLabel,
219+
description: l10n.articlesBeforeInterstitialAdsDescription,
220+
value: _getArticlesBeforeInterstitial(config, role),
221+
onChanged: (value) {
222+
widget.onConfigChanged(
223+
widget.remoteConfig.copyWith(
224+
adConfig: _updateArticlesBeforeInterstitial(config, value, role),
225+
),
226+
);
227+
},
228+
controller: _articlesToReadBeforeShowingInterstitialAdsControllers[role],
229+
),
230+
],
231+
);
232+
}
233+
234+
int _getAdFrequency(AdConfig config, AppUserRole role) {
235+
switch (role) {
236+
case AppUserRole.guestUser:
237+
return config.feedAdConfiguration.frequencyConfig.guestAdFrequency;
238+
case AppUserRole.standardUser:
239+
return config.feedAdConfiguration.frequencyConfig.authenticatedAdFrequency;
240+
case AppUserRole.premiumUser:
241+
return config.feedAdConfiguration.frequencyConfig.premiumAdFrequency;
242+
}
243+
}
244+
245+
int _getAdPlacementInterval(AdConfig config, AppUserRole role) {
246+
switch (role) {
247+
case AppUserRole.guestUser:
248+
return config.feedAdConfiguration.frequencyConfig.guestAdPlacementInterval;
249+
case AppUserRole.standardUser:
250+
return config.feedAdConfiguration.frequencyConfig.authenticatedAdPlacementInterval;
251+
case AppUserRole.premiumUser:
252+
return config.feedAdConfiguration.frequencyConfig.premiumAdPlacementInterval;
253+
}
254+
}
255+
256+
int _getArticlesBeforeInterstitial(AdConfig config, AppUserRole role) {
257+
switch (role) {
258+
case AppUserRole.guestUser:
259+
return config.articleAdConfiguration.interstitialAdConfiguration
260+
.frequencyConfig.guestArticlesToReadBeforeShowingInterstitialAds;
261+
case AppUserRole.standardUser:
262+
return config.articleAdConfiguration.interstitialAdConfiguration
263+
.frequencyConfig.standardUserArticlesToReadBeforeShowingInterstitialAds;
264+
case AppUserRole.premiumUser:
265+
return config.articleAdConfiguration.interstitialAdConfiguration
266+
.frequencyConfig.premiumUserArticlesToReadBeforeShowingInterstitialAds;
267+
}
268+
}
269+
270+
AdConfig _updateAdFrequency(AdConfig config, int value, AppUserRole role) {
271+
switch (role) {
272+
case AppUserRole.guestUser:
273+
return config.copyWith(
274+
feedAdConfiguration: config.feedAdConfiguration.copyWith(
275+
frequencyConfig: config.feedAdConfiguration.frequencyConfig
276+
.copyWith(guestAdFrequency: value),
277+
),
278+
);
279+
case AppUserRole.standardUser:
280+
return config.copyWith(
281+
feedAdConfiguration: config.feedAdConfiguration.copyWith(
282+
frequencyConfig: config.feedAdConfiguration.frequencyConfig
283+
.copyWith(authenticatedAdFrequency: value),
284+
),
285+
);
286+
case AppUserRole.premiumUser:
287+
return config.copyWith(
288+
feedAdConfiguration: config.feedAdConfiguration.copyWith(
289+
frequencyConfig: config.feedAdConfiguration.frequencyConfig
290+
.copyWith(premiumAdFrequency: value),
291+
),
292+
);
293+
}
294+
}
295+
296+
AdConfig _updateAdPlacementInterval(
297+
AdConfig config,
298+
int value,
299+
AppUserRole role,
300+
) {
301+
switch (role) {
302+
case AppUserRole.guestUser:
303+
return config.copyWith(
304+
feedAdConfiguration: config.feedAdConfiguration.copyWith(
305+
frequencyConfig: config.feedAdConfiguration.frequencyConfig
306+
.copyWith(guestAdPlacementInterval: value),
307+
),
308+
);
309+
case AppUserRole.standardUser:
310+
return config.copyWith(
311+
feedAdConfiguration: config.feedAdConfiguration.copyWith(
312+
frequencyConfig: config.feedAdConfiguration.frequencyConfig
313+
.copyWith(authenticatedAdPlacementInterval: value),
314+
),
315+
);
316+
case AppUserRole.premiumUser:
317+
return config.copyWith(
318+
feedAdConfiguration: config.feedAdConfiguration.copyWith(
319+
frequencyConfig: config.feedAdConfiguration.frequencyConfig
320+
.copyWith(premiumAdPlacementInterval: value),
321+
),
322+
);
323+
}
324+
}
325+
326+
AdConfig _updateArticlesBeforeInterstitial(
327+
AdConfig config,
328+
int value,
329+
AppUserRole role,
330+
) {
331+
final currentFrequencyConfig =
332+
config.articleAdConfiguration.interstitialAdConfiguration.frequencyConfig;
333+
334+
ArticleInterstitialAdFrequencyConfig newFrequencyConfig;
335+
336+
switch (role) {
337+
case AppUserRole.guestUser:
338+
newFrequencyConfig = currentFrequencyConfig.copyWith(
339+
guestArticlesToReadBeforeShowingInterstitialAds: value,
340+
);
341+
break;
342+
case AppUserRole.standardUser:
343+
newFrequencyConfig = currentFrequencyConfig.copyWith(
344+
standardUserArticlesToReadBeforeShowingInterstitialAds: value,
345+
);
346+
break;
347+
case AppUserRole.premiumUser:
348+
newFrequencyConfig = currentFrequencyConfig.copyWith(
349+
premiumUserArticlesToReadBeforeShowingInterstitialAds: value,
350+
);
351+
break;
352+
}
353+
354+
return config.copyWith(
355+
articleAdConfiguration: config.articleAdConfiguration.copyWith(
356+
interstitialAdConfiguration: config
357+
.articleAdConfiguration.interstitialAdConfiguration
358+
.copyWith(frequencyConfig: newFrequencyConfig),
359+
),
360+
);
361+
}
362+
}

0 commit comments

Comments
 (0)