@@ -157,7 +157,15 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
157
157
),
158
158
],
159
159
),
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
+ ),
161
169
],
162
170
),
163
171
ListView (
@@ -353,6 +361,48 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
353
361
);
354
362
}
355
363
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
+
356
406
Widget _buildAdConfigSection (
357
407
BuildContext context,
358
408
RemoteConfig remoteConfig,
@@ -886,6 +936,200 @@ class _UserPreferenceLimitsFormState extends State<_UserPreferenceLimitsForm> {
886
936
}
887
937
}
888
938
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
+
889
1133
class _AdConfigForm extends StatefulWidget {
890
1134
const _AdConfigForm ({
891
1135
required this .userRole,
0 commit comments