Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 439b81f

Browse files
authored
Merge pull request #85 from flutter-news-app-full-source-code/refactor/ad-config-ui-role-based
Refactor/ad config UI role based
2 parents 5b85ee2 + fdfe26f commit 439b81f

16 files changed

+366
-408
lines changed

‎lib/app_configuration/widgets/ad_config_form.dart‎

Lines changed: 3 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ import 'package:flutter/material.dart';
33
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
44

55
/// {@template ad_config_form}
6-
/// A form widget for configuring ad settings based on user role.
6+
/// A form widget for configuring global ad settings.
77
///
8-
/// This widget uses a [TabBar] to allow selection of an [AppUserRole]
9-
/// and then conditionally renders the relevant input fields for that role.
8+
/// This widget primarily controls the global enable/disable switch for ads.
109
/// {@endtemplate}
1110
class AdConfigForm extends StatefulWidget {
1211
/// {@macro ad_config_form}
@@ -26,96 +25,7 @@ class AdConfigForm extends StatefulWidget {
2625
State<AdConfigForm> createState() => _AdConfigFormState();
2726
}
2827

29-
class _AdConfigFormState extends State<AdConfigForm>
30-
with SingleTickerProviderStateMixin {
31-
late TabController _tabController;
32-
late final Map<AppUserRole, TextEditingController> _adFrequencyControllers;
33-
late final Map<AppUserRole, TextEditingController>
34-
_adPlacementIntervalControllers;
35-
36-
@override
37-
void initState() {
38-
super.initState();
39-
_tabController = TabController(
40-
length: AppUserRole.values.length,
41-
vsync: this,
42-
);
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:
59-
TextEditingController(
60-
text: _getAdFrequency(adConfig, role).toString(),
61-
)
62-
..selection = TextSelection.collapsed(
63-
offset: _getAdFrequency(adConfig, role).toString().length,
64-
),
65-
};
66-
_adPlacementIntervalControllers = {
67-
for (final role in AppUserRole.values)
68-
role:
69-
TextEditingController(
70-
text: _getAdPlacementInterval(adConfig, role).toString(),
71-
)
72-
..selection = TextSelection.collapsed(
73-
offset: _getAdPlacementInterval(
74-
adConfig,
75-
role,
76-
).toString().length,
77-
),
78-
};
79-
}
80-
81-
void _updateControllers() {
82-
final adConfig = widget.remoteConfig.adConfig;
83-
for (final role in AppUserRole.values) {
84-
final newFrequencyValue = _getAdFrequency(adConfig, role).toString();
85-
if (_adFrequencyControllers[role]?.text != newFrequencyValue) {
86-
_adFrequencyControllers[role]?.text = newFrequencyValue;
87-
_adFrequencyControllers[role]?.selection = TextSelection.collapsed(
88-
offset: newFrequencyValue.length,
89-
);
90-
}
91-
92-
final newPlacementIntervalValue = _getAdPlacementInterval(
93-
adConfig,
94-
role,
95-
).toString();
96-
if (_adPlacementIntervalControllers[role]?.text !=
97-
newPlacementIntervalValue) {
98-
_adPlacementIntervalControllers[role]?.text = newPlacementIntervalValue;
99-
_adPlacementIntervalControllers[role]?.selection =
100-
TextSelection.collapsed(
101-
offset: newPlacementIntervalValue.length,
102-
);
103-
}
104-
}
105-
}
106-
107-
@override
108-
void dispose() {
109-
_tabController.dispose();
110-
for (final controller in _adFrequencyControllers.values) {
111-
controller.dispose();
112-
}
113-
for (final controller in _adPlacementIntervalControllers.values) {
114-
controller.dispose();
115-
}
116-
super.dispose();
117-
}
118-
28+
class _AdConfigFormState extends State<AdConfigForm> {
11929
@override
12030
Widget build(BuildContext context) {
12131
final adConfig = widget.remoteConfig.adConfig;
@@ -138,38 +48,4 @@ class _AdConfigFormState extends State<AdConfigForm>
13848
],
13949
);
14050
}
141-
142-
int _getAdFrequency(AdConfig config, AppUserRole role) {
143-
switch (role) {
144-
case AppUserRole.guestUser:
145-
return config.feedAdConfiguration.frequencyConfig.guestAdFrequency;
146-
case AppUserRole.standardUser:
147-
return config
148-
.feedAdConfiguration
149-
.frequencyConfig
150-
.authenticatedAdFrequency;
151-
case AppUserRole.premiumUser:
152-
return config.feedAdConfiguration.frequencyConfig.premiumAdFrequency;
153-
}
154-
}
155-
156-
int _getAdPlacementInterval(AdConfig config, AppUserRole role) {
157-
switch (role) {
158-
case AppUserRole.guestUser:
159-
return config
160-
.feedAdConfiguration
161-
.frequencyConfig
162-
.guestAdPlacementInterval;
163-
case AppUserRole.standardUser:
164-
return config
165-
.feedAdConfiguration
166-
.frequencyConfig
167-
.authenticatedAdPlacementInterval;
168-
case AppUserRole.premiumUser:
169-
return config
170-
.feedAdConfiguration
171-
.frequencyConfig
172-
.premiumAdPlacementInterval;
173-
}
174-
}
17551
}

‎lib/app_configuration/widgets/article_ad_settings_form.dart‎

Lines changed: 133 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import 'package:core/core.dart';
22
import 'package:flutter/material.dart';
3+
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
34
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
45
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/banner_ad_shape_l10n.dart';
56
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/in_article_ad_slot_type_l10n.dart';
7+
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/app_user_role_l10n.dart';
68
import 'package:ui_kit/ui_kit.dart';
79

810
/// {@template article_ad_settings_form}
@@ -28,14 +30,28 @@ class ArticleAdSettingsForm extends StatefulWidget {
2830

2931
class _ArticleAdSettingsFormState extends State<ArticleAdSettingsForm>
3032
with SingleTickerProviderStateMixin {
33+
late TabController _tabController;
34+
3135
@override
3236
void initState() {
3337
super.initState();
38+
_tabController = TabController(
39+
length: AppUserRole.values.length,
40+
vsync: this,
41+
);
3442
}
3543

3644
@override
3745
void didUpdateWidget(covariant ArticleAdSettingsForm oldWidget) {
3846
super.didUpdateWidget(oldWidget);
47+
// No specific controller updates needed here as the UI rebuilds based on
48+
// the remoteConfig directly.
49+
}
50+
51+
@override
52+
void dispose() {
53+
_tabController.dispose();
54+
super.dispose();
3955
}
4056

4157
@override
@@ -134,34 +150,129 @@ class _ArticleAdSettingsFormState extends State<ArticleAdSettingsForm>
134150
textAlign: TextAlign.start,
135151
),
136152
const SizedBox(height: AppSpacing.lg),
137-
...articleAdConfig.inArticleAdSlotConfigurations.map(
138-
(slotConfig) => SwitchListTile(
139-
title: Text(slotConfig.slotType.l10n(context)),
140-
value: slotConfig.enabled,
141-
onChanged: (value) {
142-
final updatedSlots = articleAdConfig
143-
.inArticleAdSlotConfigurations
144-
.map(
145-
(e) => e.slotType == slotConfig.slotType
146-
? e.copyWith(enabled: value)
147-
: e,
148-
)
149-
.toList();
150-
widget.onConfigChanged(
151-
widget.remoteConfig.copyWith(
152-
adConfig: adConfig.copyWith(
153-
articleAdConfiguration: articleAdConfig.copyWith(
154-
inArticleAdSlotConfigurations: updatedSlots,
155-
),
153+
Align(
154+
alignment: AlignmentDirectional.centerStart,
155+
child: SizedBox(
156+
height: kTextTabBarHeight,
157+
child: TabBar(
158+
controller: _tabController,
159+
tabAlignment: TabAlignment.start,
160+
isScrollable: true,
161+
tabs: AppUserRole.values
162+
.map((role) => Tab(text: role.l10n(context)))
163+
.toList(),
164+
),
165+
),
166+
),
167+
const SizedBox(height: AppSpacing.lg),
168+
SizedBox(
169+
height: 250,
170+
child: TabBarView(
171+
controller: _tabController,
172+
children: AppUserRole.values
173+
.map(
174+
(role) => _buildRoleSpecificFields(
175+
context,
176+
l10n,
177+
role,
178+
articleAdConfig,
156179
),
157-
),
158-
);
159-
},
180+
)
181+
.toList(),
160182
),
161183
),
162184
],
163185
),
164186
],
165187
);
166188
}
189+
190+
/// Builds role-specific configuration fields for in-article ad slots.
191+
///
192+
/// This widget displays checkboxes for each [InArticleAdSlotType] for a
193+
/// given [AppUserRole], allowing to enable/disable specific ad slots.
194+
Widget _buildRoleSpecificFields(
195+
BuildContext context,
196+
AppLocalizations l10n,
197+
AppUserRole role,
198+
ArticleAdConfiguration config,
199+
) {
200+
final roleSlots = config.visibleTo[role];
201+
202+
return Column(
203+
children: [
204+
SwitchListTile(
205+
title: Text(l10n.enableInArticleAdsForRoleLabel(role.l10n(context))),
206+
value: roleSlots != null,
207+
onChanged: (value) {
208+
final newVisibleTo =
209+
Map<AppUserRole, Map<InArticleAdSlotType, bool>>.from(
210+
config.visibleTo,
211+
);
212+
if (value) {
213+
// Default values when enabling for a role
214+
newVisibleTo[role] = {
215+
InArticleAdSlotType.aboveArticleContinueReadingButton: true,
216+
InArticleAdSlotType.belowArticleContinueReadingButton: true,
217+
};
218+
} else {
219+
newVisibleTo.remove(role);
220+
}
221+
222+
widget.onConfigChanged(
223+
widget.remoteConfig.copyWith(
224+
adConfig: widget.remoteConfig.adConfig.copyWith(
225+
articleAdConfiguration: config.copyWith(
226+
visibleTo: newVisibleTo,
227+
),
228+
),
229+
),
230+
);
231+
},
232+
),
233+
if (roleSlots != null)
234+
Padding(
235+
padding: const EdgeInsets.symmetric(
236+
horizontal: AppSpacing.lg,
237+
vertical: AppSpacing.sm,
238+
),
239+
child: Column(
240+
children: [
241+
// SwitchListTile for each InArticleAdSlotType
242+
for (final slotType in InArticleAdSlotType.values)
243+
CheckboxListTile(
244+
title: Text(slotType.l10n(context)),
245+
value: roleSlots[slotType] ?? false,
246+
onChanged: (value) {
247+
final newRoleSlots = Map<InArticleAdSlotType, bool>.from(
248+
roleSlots,
249+
);
250+
if (value ?? false) {
251+
newRoleSlots[slotType] = true;
252+
} else {
253+
newRoleSlots.remove(slotType);
254+
}
255+
256+
final newVisibleTo =
257+
Map<AppUserRole, Map<InArticleAdSlotType, bool>>.from(
258+
config.visibleTo,
259+
)..[role] = newRoleSlots;
260+
261+
widget.onConfigChanged(
262+
widget.remoteConfig.copyWith(
263+
adConfig: widget.remoteConfig.adConfig.copyWith(
264+
articleAdConfiguration: config.copyWith(
265+
visibleTo: newVisibleTo,
266+
),
267+
),
268+
),
269+
);
270+
},
271+
),
272+
],
273+
),
274+
),
275+
],
276+
);
277+
}
167278
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /