@@ -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+
8891133class _AdConfigForm extends StatefulWidget {
8901134 const _AdConfigForm ({
8911135 required this .userRole,
0 commit comments