Skip to content

Commit ebbf958

Browse files
committed
feat(app_configuration): add stateful feed configuration tab with persistent expansion state
- Convert FeedConfigurationTab from StatelessWidget to StatefulWidget - Implement expansion persistence using ValueNotifier - Add unique keys to ExpansionTiles for maintaining expansion state - Update build method to use ValueListenableBuilder for state management - Improve code readability and structure for better maintainability
1 parent 844ea7d commit ebbf958

File tree

1 file changed

+112
-73
lines changed

1 file changed

+112
-73
lines changed

lib/app_configuration/view/tabs/feed_configuration_tab.dart

Lines changed: 112 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuratio
44
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/user_preference_limits_form.dart';
55
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
66
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/feed_decorator_type_l10n.dart';
7+
import 'package:flutter/foundation.dart';
78
import 'package:ui_kit/ui_kit.dart';
89

910
/// {@template feed_configuration_tab}
1011
/// A widget representing the "Feed" tab in the App Configuration page.
1112
///
1213
/// This tab allows configuration of user content limits and feed decorators.
1314
/// {@endtemplate}
14-
class FeedConfigurationTab extends StatelessWidget {
15+
class FeedConfigurationTab extends StatefulWidget {
1516
/// {@macro feed_configuration_tab}
1617
const FeedConfigurationTab({
1718
required this.remoteConfig,
@@ -25,6 +26,22 @@ class FeedConfigurationTab extends StatelessWidget {
2526
/// Callback to notify parent of changes to the [RemoteConfig].
2627
final ValueChanged<RemoteConfig> onConfigChanged;
2728

29+
@override
30+
State<FeedConfigurationTab> createState() => _FeedConfigurationTabState();
31+
}
32+
33+
class _FeedConfigurationTabState extends State<FeedConfigurationTab> {
34+
/// Notifier for the index of the currently expanded top-level ExpansionTile.
35+
///
36+
/// A value of `null` means no tile is expanded.
37+
final ValueNotifier<int?> _expandedTileIndex = ValueNotifier<int?>(null);
38+
39+
@override
40+
void dispose() {
41+
_expandedTileIndex.dispose();
42+
super.dispose();
43+
}
44+
2845
@override
2946
Widget build(BuildContext context) {
3047
final l10n = AppLocalizationsX(context).l10n;
@@ -33,86 +50,108 @@ class FeedConfigurationTab extends StatelessWidget {
3350
padding: const EdgeInsets.all(AppSpacing.lg),
3451
children: [
3552
// Top-level ExpansionTile for User Content Limits
36-
ExpansionTile(
37-
title: Text(l10n.userContentLimitsTitle),
38-
childrenPadding: const EdgeInsetsDirectional.only(
39-
start: AppSpacing.lg, // Adjusted padding for hierarchy
40-
top: AppSpacing.md,
41-
bottom: AppSpacing.md,
42-
),
43-
expandedCrossAxisAlignment: CrossAxisAlignment.start, // Align content to start
44-
children: [
45-
UserPreferenceLimitsForm(
46-
remoteConfig: remoteConfig,
47-
onConfigChanged: onConfigChanged,
48-
),
49-
],
53+
ValueListenableBuilder<int?>(
54+
valueListenable: _expandedTileIndex,
55+
builder: (context, expandedIndex, child) {
56+
const tileIndex = 0;
57+
return ExpansionTile(
58+
key: ValueKey('userContentLimitsTile_$expandedIndex'),
59+
title: Text(l10n.userContentLimitsTitle),
60+
childrenPadding: const EdgeInsetsDirectional.only(
61+
start: AppSpacing.lg,
62+
top: AppSpacing.md,
63+
bottom: AppSpacing.md,
64+
),
65+
expandedCrossAxisAlignment: CrossAxisAlignment.start,
66+
onExpansionChanged: (isExpanded) {
67+
_expandedTileIndex.value = isExpanded ? tileIndex : null;
68+
},
69+
initiallyExpanded: expandedIndex == tileIndex,
70+
children: [
71+
UserPreferenceLimitsForm(
72+
remoteConfig: widget.remoteConfig,
73+
onConfigChanged: widget.onConfigChanged,
74+
),
75+
],
76+
);
77+
},
5078
),
5179
const SizedBox(height: AppSpacing.lg),
5280
// New Top-level ExpansionTile for Feed Decorators
53-
ExpansionTile(
54-
title: Text(l10n.feedDecoratorsTitle),
55-
childrenPadding: const EdgeInsetsDirectional.only(
56-
start: AppSpacing.lg, // Adjusted padding for hierarchy
57-
top: AppSpacing.md,
58-
bottom: AppSpacing.md,
59-
),
60-
expandedCrossAxisAlignment: CrossAxisAlignment.start, // Align content to start
61-
children: [
62-
Text(
63-
l10n.feedDecoratorsDescription,
64-
style: Theme.of(context).textTheme.bodySmall?.copyWith(
65-
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
81+
ValueListenableBuilder<int?>(
82+
valueListenable: _expandedTileIndex,
83+
builder: (context, expandedIndex, child) {
84+
const tileIndex = 1;
85+
return ExpansionTile(
86+
key: ValueKey('feedDecoratorsTile_$expandedIndex'),
87+
title: Text(l10n.feedDecoratorsTitle),
88+
childrenPadding: const EdgeInsetsDirectional.only(
89+
start: AppSpacing.lg,
90+
top: AppSpacing.md,
91+
bottom: AppSpacing.md,
6692
),
67-
),
68-
const SizedBox(height: AppSpacing.lg),
69-
// Individual ExpansionTiles for each Feed Decorator, nested
70-
for (final decoratorType in FeedDecoratorType.values)
71-
Padding(
72-
padding: const EdgeInsets.only(bottom: AppSpacing.md),
73-
child: ExpansionTile(
74-
title: Text(decoratorType.l10n(context)),
75-
childrenPadding: const EdgeInsetsDirectional.only(
76-
start: AppSpacing.xl, // Further adjusted padding for nested hierarchy
77-
top: AppSpacing.md,
78-
bottom: AppSpacing.md,
93+
expandedCrossAxisAlignment: CrossAxisAlignment.start,
94+
onExpansionChanged: (isExpanded) {
95+
_expandedTileIndex.value = isExpanded ? tileIndex : null;
96+
},
97+
initiallyExpanded: expandedIndex == tileIndex,
98+
children: [
99+
Text(
100+
l10n.feedDecoratorsDescription,
101+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
102+
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
79103
),
80-
expandedCrossAxisAlignment: CrossAxisAlignment.start, // Align content to start
81-
children: [
82-
FeedDecoratorForm(
83-
decoratorType: decoratorType,
84-
remoteConfig: remoteConfig.copyWith(
85-
feedDecoratorConfig:
86-
Map.from(
87-
remoteConfig.feedDecoratorConfig,
88-
)..putIfAbsent(
89-
decoratorType,
90-
() => FeedDecoratorConfig(
91-
category:
92-
decoratorType ==
93-
FeedDecoratorType.suggestedTopics ||
104+
),
105+
const SizedBox(height: AppSpacing.lg),
106+
// Individual ExpansionTiles for each Feed Decorator, nested
107+
for (final decoratorType in FeedDecoratorType.values)
108+
Padding(
109+
padding: const EdgeInsets.only(bottom: AppSpacing.md),
110+
child: ExpansionTile(
111+
title: Text(decoratorType.l10n(context)),
112+
childrenPadding: const EdgeInsetsDirectional.only(
113+
start: AppSpacing.xl,
114+
top: AppSpacing.md,
115+
bottom: AppSpacing.md,
116+
),
117+
expandedCrossAxisAlignment: CrossAxisAlignment.start,
118+
children: [
119+
FeedDecoratorForm(
120+
decoratorType: decoratorType,
121+
remoteConfig: widget.remoteConfig.copyWith(
122+
feedDecoratorConfig:
123+
Map.from(
124+
widget.remoteConfig.feedDecoratorConfig,
125+
)..putIfAbsent(
126+
decoratorType,
127+
() => FeedDecoratorConfig(
128+
category:
94129
decoratorType ==
95-
FeedDecoratorType.suggestedSources
96-
? FeedDecoratorCategory.contentCollection
97-
: FeedDecoratorCategory.callToAction,
98-
enabled: false,
99-
visibleTo: const {},
100-
itemsToDisplay:
101-
decoratorType ==
102-
FeedDecoratorType.suggestedTopics ||
130+
FeedDecoratorType.suggestedTopics ||
131+
decoratorType ==
132+
FeedDecoratorType.suggestedSources
133+
? FeedDecoratorCategory.contentCollection
134+
: FeedDecoratorCategory.callToAction,
135+
enabled: false,
136+
visibleTo: const {},
137+
itemsToDisplay:
103138
decoratorType ==
104-
FeedDecoratorType.suggestedSources
105-
? 0
106-
: null,
107-
),
108-
),
109-
),
110-
onConfigChanged: onConfigChanged,
139+
FeedDecoratorType.suggestedTopics ||
140+
decoratorType ==
141+
FeedDecoratorType.suggestedSources
142+
? 0
143+
: null,
144+
),
145+
),
146+
),
147+
onConfigChanged: widget.onConfigChanged,
148+
),
149+
],
111150
),
112-
],
113-
),
114-
),
115-
],
151+
),
152+
],
153+
);
154+
},
116155
),
117156
],
118157
);

0 commit comments

Comments
 (0)