Skip to content

Commit 489e068

Browse files
committed
refactor(app_configuration): make GeneralConfigurationTab a StatefulWidget
- Convert GeneralConfigurationTab from StatelessWidget to StatefulWidget - Add ValueNotifier to manage expanded tile index - Implement proper dispose logic for ValueNotifier - Wrap ExpansionTile with ValueListenableBuilder for state management - Add onExpansionChanged callback to update expanded tile index - Set initial expansion state based on ValueNotifier value - Update widget references to reflect StatefulWidget changes
1 parent ebbf958 commit 489e068

File tree

1 file changed

+150
-111
lines changed

1 file changed

+150
-111
lines changed

lib/app_configuration/view/tabs/general_configuration_tab.dart

Lines changed: 150 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import 'package:core/core.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/app_config_form_fields.dart';
44
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
5+
import 'package:flutter/foundation.dart';
56
import 'package:ui_kit/ui_kit.dart';
67

78
/// {@template general_configuration_tab}
89
/// A widget representing the "General" tab in the App Configuration page.
910
///
1011
/// This tab allows configuration of maintenance mode and force update settings.
1112
/// {@endtemplate}
12-
class GeneralConfigurationTab extends StatelessWidget {
13+
class GeneralConfigurationTab extends StatefulWidget {
1314
/// {@macro general_configuration_tab}
1415
const GeneralConfigurationTab({
1516
required this.remoteConfig,
@@ -23,6 +24,22 @@ class GeneralConfigurationTab extends StatelessWidget {
2324
/// Callback to notify parent of changes to the [RemoteConfig].
2425
final ValueChanged<RemoteConfig> onConfigChanged;
2526

27+
@override
28+
State<GeneralConfigurationTab> createState() => _GeneralConfigurationTabState();
29+
}
30+
31+
class _GeneralConfigurationTabState extends State<GeneralConfigurationTab> {
32+
/// Notifier for the index of the currently expanded top-level ExpansionTile.
33+
///
34+
/// A value of `null` means no tile is expanded.
35+
final ValueNotifier<int?> _expandedTileIndex = ValueNotifier<int?>(null);
36+
37+
@override
38+
void dispose() {
39+
_expandedTileIndex.dispose();
40+
super.dispose();
41+
}
42+
2643
@override
2744
Widget build(BuildContext context) {
2845
final l10n = AppLocalizationsX(context).l10n;
@@ -31,127 +48,149 @@ class GeneralConfigurationTab extends StatelessWidget {
3148
padding: const EdgeInsets.all(AppSpacing.lg),
3249
children: [
3350
// Top-level ExpansionTile for Maintenance Section
34-
ExpansionTile(
35-
title: Text(l10n.maintenanceModeTitle),
36-
childrenPadding: const EdgeInsetsDirectional.only(
37-
start: AppSpacing.lg, // Adjusted padding for hierarchy
38-
top: AppSpacing.md,
39-
bottom: AppSpacing.md,
40-
),
41-
expandedCrossAxisAlignment: CrossAxisAlignment.start, // Align content to start
42-
children: [
43-
Column(
44-
crossAxisAlignment: CrossAxisAlignment.start,
51+
ValueListenableBuilder<int?>(
52+
valueListenable: _expandedTileIndex,
53+
builder: (context, expandedIndex, child) {
54+
const tileIndex = 0;
55+
return ExpansionTile(
56+
key: ValueKey('maintenanceModeTile_$expandedIndex'),
57+
title: Text(l10n.maintenanceModeTitle),
58+
childrenPadding: const EdgeInsetsDirectional.only(
59+
start: AppSpacing.lg,
60+
top: AppSpacing.md,
61+
bottom: AppSpacing.md,
62+
),
63+
expandedCrossAxisAlignment: CrossAxisAlignment.start,
64+
onExpansionChanged: (isExpanded) {
65+
_expandedTileIndex.value = isExpanded ? tileIndex : null;
66+
},
67+
initiallyExpanded: expandedIndex == tileIndex,
4568
children: [
46-
Text(
47-
l10n.maintenanceModeDescription,
48-
style: Theme.of(context).textTheme.bodySmall?.copyWith(
49-
color: Theme.of(
50-
context,
51-
).colorScheme.onSurface.withOpacity(0.7),
52-
),
53-
),
54-
const SizedBox(height: AppSpacing.lg),
55-
SwitchListTile(
56-
title: Text(l10n.isUnderMaintenanceLabel),
57-
subtitle: Text(l10n.isUnderMaintenanceDescription),
58-
value: remoteConfig.appStatus.isUnderMaintenance,
59-
onChanged: (value) {
60-
onConfigChanged(
61-
remoteConfig.copyWith(
62-
appStatus: remoteConfig.appStatus.copyWith(
63-
isUnderMaintenance: value,
64-
),
69+
Column(
70+
crossAxisAlignment: CrossAxisAlignment.start,
71+
children: [
72+
Text(
73+
l10n.maintenanceModeDescription,
74+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
75+
color: Theme.of(
76+
context,
77+
).colorScheme.onSurface.withOpacity(0.7),
6578
),
66-
);
67-
},
79+
),
80+
const SizedBox(height: AppSpacing.lg),
81+
SwitchListTile(
82+
title: Text(l10n.isUnderMaintenanceLabel),
83+
subtitle: Text(l10n.isUnderMaintenanceDescription),
84+
value: widget.remoteConfig.appStatus.isUnderMaintenance,
85+
onChanged: (value) {
86+
widget.onConfigChanged(
87+
widget.remoteConfig.copyWith(
88+
appStatus: widget.remoteConfig.appStatus.copyWith(
89+
isUnderMaintenance: value,
90+
),
91+
),
92+
);
93+
},
94+
),
95+
],
6896
),
6997
],
70-
),
71-
],
98+
);
99+
},
72100
),
73101
const SizedBox(height: AppSpacing.lg),
74102
// Top-level ExpansionTile for Force Update Section
75-
ExpansionTile(
76-
title: Text(l10n.forceUpdateTitle),
77-
childrenPadding: const EdgeInsetsDirectional.only(
78-
start: AppSpacing.lg, // Adjusted padding for hierarchy
79-
top: AppSpacing.md,
80-
bottom: AppSpacing.md,
81-
),
82-
expandedCrossAxisAlignment: CrossAxisAlignment.start, // Align content to start
83-
children: [
84-
Column(
85-
crossAxisAlignment: CrossAxisAlignment.start,
103+
ValueListenableBuilder<int?>(
104+
valueListenable: _expandedTileIndex,
105+
builder: (context, expandedIndex, child) {
106+
const tileIndex = 1;
107+
return ExpansionTile(
108+
key: ValueKey('forceUpdateTile_$expandedIndex'),
109+
title: Text(l10n.forceUpdateTitle),
110+
childrenPadding: const EdgeInsetsDirectional.only(
111+
start: AppSpacing.lg,
112+
top: AppSpacing.md,
113+
bottom: AppSpacing.md,
114+
),
115+
expandedCrossAxisAlignment: CrossAxisAlignment.start,
116+
onExpansionChanged: (isExpanded) {
117+
_expandedTileIndex.value = isExpanded ? tileIndex : null;
118+
},
119+
initiallyExpanded: expandedIndex == tileIndex,
86120
children: [
87-
Text(
88-
l10n.forceUpdateDescription,
89-
style: Theme.of(context).textTheme.bodySmall?.copyWith(
90-
color: Theme.of(
91-
context,
92-
).colorScheme.onSurface.withOpacity(0.7),
93-
),
94-
),
95-
const SizedBox(height: AppSpacing.lg),
96-
AppConfigTextField(
97-
label: l10n.latestAppVersionLabel,
98-
description: l10n.latestAppVersionDescription,
99-
value: remoteConfig.appStatus.latestAppVersion,
100-
onChanged: (value) {
101-
onConfigChanged(
102-
remoteConfig.copyWith(
103-
appStatus: remoteConfig.appStatus.copyWith(
104-
latestAppVersion: value,
105-
),
106-
),
107-
);
108-
},
109-
),
110-
SwitchListTile(
111-
title: Text(l10n.isLatestVersionOnlyLabel),
112-
subtitle: Text(l10n.isLatestVersionOnlyDescription),
113-
value: remoteConfig.appStatus.isLatestVersionOnly,
114-
onChanged: (value) {
115-
onConfigChanged(
116-
remoteConfig.copyWith(
117-
appStatus: remoteConfig.appStatus.copyWith(
118-
isLatestVersionOnly: value,
119-
),
120-
),
121-
);
122-
},
123-
),
124-
AppConfigTextField(
125-
label: l10n.iosUpdateUrlLabel,
126-
description: l10n.iosUpdateUrlDescription,
127-
value: remoteConfig.appStatus.iosUpdateUrl,
128-
onChanged: (value) {
129-
onConfigChanged(
130-
remoteConfig.copyWith(
131-
appStatus: remoteConfig.appStatus.copyWith(
132-
iosUpdateUrl: value,
133-
),
134-
),
135-
);
136-
},
137-
),
138-
AppConfigTextField(
139-
label: l10n.androidUpdateUrlLabel,
140-
description: l10n.androidUpdateUrlDescription,
141-
value: remoteConfig.appStatus.androidUpdateUrl,
142-
onChanged: (value) {
143-
onConfigChanged(
144-
remoteConfig.copyWith(
145-
appStatus: remoteConfig.appStatus.copyWith(
146-
androidUpdateUrl: value,
147-
),
121+
Column(
122+
crossAxisAlignment: CrossAxisAlignment.start,
123+
children: [
124+
Text(
125+
l10n.forceUpdateDescription,
126+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
127+
color: Theme.of(
128+
context,
129+
).colorScheme.onSurface.withOpacity(0.7),
148130
),
149-
);
150-
},
131+
),
132+
const SizedBox(height: AppSpacing.lg),
133+
AppConfigTextField(
134+
label: l10n.latestAppVersionLabel,
135+
description: l10n.latestAppVersionDescription,
136+
value: widget.remoteConfig.appStatus.latestAppVersion,
137+
onChanged: (value) {
138+
widget.onConfigChanged(
139+
widget.remoteConfig.copyWith(
140+
appStatus: widget.remoteConfig.appStatus.copyWith(
141+
latestAppVersion: value,
142+
),
143+
),
144+
);
145+
},
146+
),
147+
SwitchListTile(
148+
title: Text(l10n.isLatestVersionOnlyLabel),
149+
subtitle: Text(l10n.isLatestVersionOnlyDescription),
150+
value: widget.remoteConfig.appStatus.isLatestVersionOnly,
151+
onChanged: (value) {
152+
widget.onConfigChanged(
153+
widget.remoteConfig.copyWith(
154+
appStatus: widget.remoteConfig.appStatus.copyWith(
155+
isLatestVersionOnly: value,
156+
),
157+
),
158+
);
159+
},
160+
),
161+
AppConfigTextField(
162+
label: l10n.iosUpdateUrlLabel,
163+
description: l10n.iosUpdateUrlDescription,
164+
value: widget.remoteConfig.appStatus.iosUpdateUrl,
165+
onChanged: (value) {
166+
widget.onConfigChanged(
167+
widget.remoteConfig.copyWith(
168+
appStatus: widget.remoteConfig.appStatus.copyWith(
169+
iosUpdateUrl: value,
170+
),
171+
),
172+
);
173+
},
174+
),
175+
AppConfigTextField(
176+
label: l10n.androidUpdateUrlLabel,
177+
description: l10n.androidUpdateUrlDescription,
178+
value: widget.remoteConfig.appStatus.androidUpdateUrl,
179+
onChanged: (value) {
180+
widget.onConfigChanged(
181+
widget.remoteConfig.copyWith(
182+
appStatus: widget.remoteConfig.appStatus.copyWith(
183+
androidUpdateUrl: value,
184+
),
185+
),
186+
);
187+
},
188+
),
189+
],
151190
),
152191
],
153-
),
154-
],
192+
);
193+
},
155194
),
156195
],
157196
);

0 commit comments

Comments
 (0)