Skip to content

Commit 086698f

Browse files
committed
feat(app-configuration): add feed decorators configuration section
- Implement new UI section for configuring feed decorators - Add ExpansionTile for each feed decorator type - Include form fields for enabling/disabling decorators, items to display, and role-specific settings - Update RemoteConfig with new feed decorator configurations - Ensure proper state management and data binding for form inputs
1 parent 431ae16 commit 086698f

File tree

1 file changed

+245
-1
lines changed

1 file changed

+245
-1
lines changed

lib/app_configuration/view/app_configuration_page.dart

Lines changed: 245 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,15 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
157157
),
158158
],
159159
),
160-
const SizedBox.shrink(),
160+
ExpansionTile(
161+
title: Text(l10n.feedDecoratorsTitle),
162+
childrenPadding: const EdgeInsets.symmetric(
163+
horizontal: AppSpacing.xxl,
164+
),
165+
children: [
166+
_buildFeedDecoratorConfigSection(context, remoteConfig),
167+
],
168+
),
161169
],
162170
),
163171
ListView(
@@ -353,6 +361,48 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
353361
);
354362
}
355363

364+
Widget _buildFeedDecoratorConfigSection(
365+
BuildContext context,
366+
RemoteConfig remoteConfig,
367+
) {
368+
final l10n = AppLocalizationsX(context).l10n;
369+
final decoratorConfigs = remoteConfig.feedDecoratorConfig.entries.toList();
370+
371+
return Column(
372+
crossAxisAlignment: CrossAxisAlignment.start,
373+
children: [
374+
Text(
375+
l10n.feedDecoratorsDescription,
376+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
377+
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
378+
),
379+
),
380+
const SizedBox(height: AppSpacing.lg),
381+
for (final decoratorEntry in decoratorConfigs)
382+
ExpansionTile(
383+
title: Text(
384+
decoratorEntry.key.name.toUpperCase(),
385+
),
386+
childrenPadding: const EdgeInsets.symmetric(
387+
horizontal: AppSpacing.xxl,
388+
),
389+
children: [
390+
_FeedDecoratorForm(
391+
decoratorType: decoratorEntry.key,
392+
remoteConfig: remoteConfig,
393+
onConfigChanged: (newConfig) {
394+
context.read<AppConfigurationBloc>().add(
395+
AppConfigurationFieldChanged(remoteConfig: newConfig),
396+
);
397+
},
398+
buildIntField: _buildIntField,
399+
),
400+
],
401+
),
402+
],
403+
);
404+
}
405+
356406
Widget _buildAdConfigSection(
357407
BuildContext context,
358408
RemoteConfig remoteConfig,
@@ -886,6 +936,200 @@ class _UserPreferenceLimitsFormState extends State<_UserPreferenceLimitsForm> {
886936
}
887937
}
888938

939+
class _FeedDecoratorForm extends StatefulWidget {
940+
const _FeedDecoratorForm({
941+
required this.decoratorType,
942+
required this.remoteConfig,
943+
required this.onConfigChanged,
944+
required this.buildIntField,
945+
});
946+
947+
final FeedDecoratorType decoratorType;
948+
final RemoteConfig remoteConfig;
949+
final ValueChanged<RemoteConfig> onConfigChanged;
950+
final Widget Function(
951+
BuildContext context, {
952+
required String label,
953+
required String description,
954+
required int value,
955+
required ValueChanged<int> onChanged,
956+
TextEditingController? controller,
957+
}) buildIntField;
958+
959+
@override
960+
State<_FeedDecoratorForm> createState() => _FeedDecoratorFormState();
961+
}
962+
963+
class _FeedDecoratorFormState extends State<_FeedDecoratorForm> {
964+
late final TextEditingController _itemsToDisplayController;
965+
late final Map<AppUserRole, TextEditingController> _roleControllers;
966+
967+
@override
968+
void initState() {
969+
super.initState();
970+
_initializeControllers();
971+
}
972+
973+
@override
974+
void didUpdateWidget(covariant _FeedDecoratorForm oldWidget) {
975+
super.didUpdateWidget(oldWidget);
976+
if (widget.remoteConfig.feedDecoratorConfig[widget.decoratorType] !=
977+
oldWidget.remoteConfig.feedDecoratorConfig[widget.decoratorType]) {
978+
_updateControllers();
979+
}
980+
}
981+
982+
void _initializeControllers() {
983+
final decoratorConfig =
984+
widget.remoteConfig.feedDecoratorConfig[widget.decoratorType]!;
985+
_itemsToDisplayController = TextEditingController(
986+
text: decoratorConfig.itemsToDisplay?.toString() ?? '',
987+
);
988+
989+
_roleControllers = {
990+
for (final role in AppUserRole.values)
991+
role: TextEditingController(
992+
text: decoratorConfig.visibleTo[role]?.daysBetweenViews.toString() ??
993+
'',
994+
),
995+
};
996+
}
997+
998+
void _updateControllers() {
999+
final decoratorConfig =
1000+
widget.remoteConfig.feedDecoratorConfig[widget.decoratorType]!;
1001+
_itemsToDisplayController.text =
1002+
decoratorConfig.itemsToDisplay?.toString() ?? '';
1003+
for (final role in AppUserRole.values) {
1004+
_roleControllers[role]?.text =
1005+
decoratorConfig.visibleTo[role]?.daysBetweenViews.toString() ?? '';
1006+
}
1007+
}
1008+
1009+
@override
1010+
void dispose() {
1011+
_itemsToDisplayController.dispose();
1012+
for (final controller in _roleControllers.values) {
1013+
controller.dispose();
1014+
}
1015+
super.dispose();
1016+
}
1017+
1018+
@override
1019+
Widget build(BuildContext context) {
1020+
final l10n = AppLocalizationsX(context).l10n;
1021+
final decoratorConfig =
1022+
widget.remoteConfig.feedDecoratorConfig[widget.decoratorType]!;
1023+
1024+
return Column(
1025+
children: [
1026+
SwitchListTile(
1027+
title: Text(l10n.enabledLabel),
1028+
value: decoratorConfig.enabled,
1029+
onChanged: (value) {
1030+
final newDecoratorConfig = decoratorConfig.copyWith(enabled: value);
1031+
final newFeedDecoratorConfig =
1032+
Map<FeedDecoratorType, FeedDecoratorConfig>.from(
1033+
widget.remoteConfig.feedDecoratorConfig,
1034+
)..[widget.decoratorType] = newDecoratorConfig;
1035+
widget.onConfigChanged(
1036+
widget.remoteConfig.copyWith(
1037+
feedDecoratorConfig: newFeedDecoratorConfig,
1038+
),
1039+
);
1040+
},
1041+
),
1042+
if (decoratorConfig.category ==
1043+
FeedDecoratorCategory.contentCollection)
1044+
widget.buildIntField(
1045+
context,
1046+
label: l10n.itemsToDisplayLabel,
1047+
description: l10n.itemsToDisplayDescription,
1048+
value: decoratorConfig.itemsToDisplay ?? 0,
1049+
onChanged: (value) {
1050+
final newDecoratorConfig =
1051+
decoratorConfig.copyWith(itemsToDisplay: value);
1052+
final newFeedDecoratorConfig =
1053+
Map<FeedDecoratorType, FeedDecoratorConfig>.from(
1054+
widget.remoteConfig.feedDecoratorConfig,
1055+
)..[widget.decoratorType] = newDecoratorConfig;
1056+
widget.onConfigChanged(
1057+
widget.remoteConfig.copyWith(
1058+
feedDecoratorConfig: newFeedDecoratorConfig,
1059+
),
1060+
);
1061+
},
1062+
controller: _itemsToDisplayController,
1063+
),
1064+
ExpansionTile(
1065+
title: Text(l10n.roleSpecificSettingsTitle),
1066+
children: AppUserRole.values.map((role) {
1067+
final roleConfig = decoratorConfig.visibleTo[role];
1068+
return CheckboxListTile(
1069+
title: Text(role.name),
1070+
value: roleConfig != null,
1071+
onChanged: (value) {
1072+
final newVisibleTo =
1073+
Map<AppUserRole, FeedDecoratorRoleConfig>.from(
1074+
decoratorConfig.visibleTo,
1075+
);
1076+
if (value == true) {
1077+
newVisibleTo[role] =
1078+
const FeedDecoratorRoleConfig(daysBetweenViews: 7);
1079+
} else {
1080+
newVisibleTo.remove(role);
1081+
}
1082+
final newDecoratorConfig =
1083+
decoratorConfig.copyWith(visibleTo: newVisibleTo);
1084+
final newFeedDecoratorConfig =
1085+
Map<FeedDecoratorType, FeedDecoratorConfig>.from(
1086+
widget.remoteConfig.feedDecoratorConfig,
1087+
)..[widget.decoratorType] = newDecoratorConfig;
1088+
widget.onConfigChanged(
1089+
widget.remoteConfig.copyWith(
1090+
feedDecoratorConfig: newFeedDecoratorConfig,
1091+
),
1092+
);
1093+
},
1094+
secondary: SizedBox(
1095+
width: 100,
1096+
child: widget.buildIntField(
1097+
context,
1098+
label: l10n.daysBetweenViewsLabel,
1099+
description: '',
1100+
value: roleConfig?.daysBetweenViews ?? 0,
1101+
onChanged: (value) {
1102+
if (roleConfig != null) {
1103+
final newRoleConfig =
1104+
roleConfig.copyWith(daysBetweenViews: value);
1105+
final newVisibleTo =
1106+
Map<AppUserRole, FeedDecoratorRoleConfig>.from(
1107+
decoratorConfig.visibleTo,
1108+
)..[role] = newRoleConfig;
1109+
final newDecoratorConfig =
1110+
decoratorConfig.copyWith(visibleTo: newVisibleTo);
1111+
final newFeedDecoratorConfig =
1112+
Map<FeedDecoratorType, FeedDecoratorConfig>.from(
1113+
widget.remoteConfig.feedDecoratorConfig,
1114+
)..[widget.decoratorType] = newDecoratorConfig;
1115+
widget.onConfigChanged(
1116+
widget.remoteConfig.copyWith(
1117+
feedDecoratorConfig: newFeedDecoratorConfig,
1118+
),
1119+
);
1120+
}
1121+
},
1122+
controller: _roleControllers[role],
1123+
),
1124+
),
1125+
);
1126+
}).toList(),
1127+
),
1128+
],
1129+
);
1130+
}
1131+
}
1132+
8891133
class _AdConfigForm extends StatefulWidget {
8901134
const _AdConfigForm({
8911135
required this.userRole,

0 commit comments

Comments
 (0)