@@ -2,14 +2,15 @@ import 'package:core/core.dart';
22import 'package:flutter/material.dart' ;
33import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/app_config_form_fields.dart' ;
44import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart' ;
5+ import 'package:flutter/foundation.dart' ;
56import '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