@@ -45,11 +45,73 @@ class _SourceFilterView extends StatefulWidget {
4545}
4646
4747class _SourceFilterViewState extends State <_SourceFilterView > {
48- // Local state to hold the filter criteria for the source list.
49- // These are managed by the dedicated SourceListFilterPage and are used
50- // only for filtering the UI in this page, not for the final headline query.
51- Set <Country > _filteredHeadquarterCountries = {};
52- Set <SourceType > _filteredSourceTypes = {};
48+ Widget _buildSourcesList (
49+ BuildContext context,
50+ HeadlinesFilterState filterState,
51+ AppLocalizations l10n,
52+ TextTheme textTheme,
53+ List <Source > displayableSources,
54+ ) {
55+ if (filterState.status == HeadlinesFilterStatus .loading &&
56+ displayableSources.isEmpty) {
57+ return const Center (child: CircularProgressIndicator ());
58+ }
59+ if (filterState.status == HeadlinesFilterStatus .failure &&
60+ displayableSources.isEmpty) {
61+ return FailureStateWidget (
62+ exception:
63+ filterState.error ??
64+ const UnknownException ('Failed to load displayable sources.' ),
65+ onRetry: () {
66+ context.read <HeadlinesFilterBloc >().add (
67+ FilterDataLoaded (
68+ initialSelectedTopics: filterState.selectedTopics.toList (),
69+ initialSelectedSources: filterState.selectedSources.toList (),
70+ initialSelectedCountries: filterState.selectedCountries.toList (),
71+ ),
72+ );
73+ },
74+ );
75+ }
76+ if (displayableSources.isEmpty &&
77+ filterState.status != HeadlinesFilterStatus .loading) {
78+ return Center (
79+ child: Padding (
80+ padding: const EdgeInsets .all (AppSpacing .paddingLarge),
81+ child: Text (
82+ l10n.headlinesFeedFilterNoSourcesMatch,
83+ style: textTheme.bodyLarge,
84+ textAlign: TextAlign .center,
85+ ),
86+ ),
87+ );
88+ }
89+
90+ return ListView .builder (
91+ padding: const EdgeInsets .symmetric (
92+ vertical: AppSpacing .paddingSmall,
93+ ).copyWith (bottom: AppSpacing .xxl),
94+ itemCount: displayableSources.length,
95+ itemBuilder: (context, index) {
96+ final source = displayableSources[index];
97+ return CheckboxListTile (
98+ title: Text (source.name, style: textTheme.titleMedium),
99+ value: filterState.selectedSources.contains (source),
100+ onChanged: (bool ? value) {
101+ if (value != null ) {
102+ context.read <HeadlinesFilterBloc >().add (
103+ FilterSourceToggled (source: source, isSelected: value),
104+ );
105+ }
106+ },
107+ controlAffinity: ListTileControlAffinity .leading,
108+ contentPadding: const EdgeInsets .symmetric (
109+ horizontal: AppSpacing .paddingMedium,
110+ ),
111+ );
112+ },
113+ );
114+ }
53115
54116 @override
55117 Widget build (BuildContext context) {
@@ -81,19 +143,20 @@ class _SourceFilterViewState extends State<_SourceFilterView> {
81143 .toList ()
82144 ..sort ((a, b) => a.name.compareTo (b.name)),
83145 'initialSelectedHeadquarterCountries' :
84- _filteredHeadquarterCountries ,
85- 'initialSelectedSourceTypes' : _filteredSourceTypes ,
146+ filterState.selectedSourceHeadquarterCountries ,
147+ 'initialSelectedSourceTypes' : filterState.selectedSourceTypes ,
86148 },
87149 );
88150
89151 // When the filter page returns with new criteria, update the
90- // local state to re-render the list.
152+ // bloc to re-render the list.
91153 if (result != null && mounted) {
92- setState (() {
93- _filteredHeadquarterCountries =
94- result['countries' ] as Set <Country >;
95- _filteredSourceTypes = result['types' ] as Set <SourceType >;
96- });
154+ context.read <HeadlinesFilterBloc >().add (
155+ FilterSourceCriteriaChanged (
156+ selectedCountries: result['countries' ] as Set <Country >,
157+ selectedSourceTypes: result['types' ] as Set <SourceType >,
158+ ),
159+ );
97160 }
98161 },
99162 ),
@@ -145,15 +208,15 @@ class _SourceFilterViewState extends State<_SourceFilterView> {
145208 final displayableSources = filterState.allSources.where ((source) {
146209 // Filter by headquarters country.
147210 final matchesCountry =
148- _filteredHeadquarterCountries .isEmpty ||
149- _filteredHeadquarterCountries .any (
211+ filterState.selectedSourceHeadquarterCountries .isEmpty ||
212+ filterState.selectedSourceHeadquarterCountries .any (
150213 (c) => c.isoCode == source.headquarters.isoCode,
151214 );
152215
153216 // Filter by source type.
154217 final matchesType =
155- _filteredSourceTypes .isEmpty ||
156- _filteredSourceTypes .contains (source.sourceType);
218+ filterState.selectedSourceTypes .isEmpty ||
219+ filterState.selectedSourceTypes .contains (source.sourceType);
157220 return matchesCountry && matchesType;
158221 }).toList ();
159222
@@ -183,72 +246,4 @@ class _SourceFilterViewState extends State<_SourceFilterView> {
183246 ),
184247 );
185248 }
186-
187- Widget _buildSourcesList (
188- BuildContext context,
189- HeadlinesFilterState filterState,
190- AppLocalizations l10n,
191- TextTheme textTheme,
192- List <Source > displayableSources,
193- ) {
194- if (filterState.status == HeadlinesFilterStatus .loading &&
195- displayableSources.isEmpty) {
196- return const Center (child: CircularProgressIndicator ());
197- }
198- if (filterState.status == HeadlinesFilterStatus .failure &&
199- displayableSources.isEmpty) {
200- return FailureStateWidget (
201- exception:
202- filterState.error ??
203- const UnknownException ('Failed to load displayable sources.' ),
204- onRetry: () {
205- context.read <HeadlinesFilterBloc >().add (
206- FilterDataLoaded (
207- initialSelectedTopics: filterState.selectedTopics.toList (),
208- initialSelectedSources: filterState.selectedSources.toList (),
209- initialSelectedCountries: filterState.selectedCountries.toList (),
210- ),
211- );
212- },
213- );
214- }
215- if (displayableSources.isEmpty &&
216- filterState.status != HeadlinesFilterStatus .loading) {
217- return Center (
218- child: Padding (
219- padding: const EdgeInsets .all (AppSpacing .paddingLarge),
220- child: Text (
221- l10n.headlinesFeedFilterNoSourcesMatch,
222- style: textTheme.bodyLarge,
223- textAlign: TextAlign .center,
224- ),
225- ),
226- );
227- }
228-
229- return ListView .builder (
230- padding: const EdgeInsets .symmetric (
231- vertical: AppSpacing .paddingSmall,
232- ).copyWith (bottom: AppSpacing .xxl),
233- itemCount: displayableSources.length,
234- itemBuilder: (context, index) {
235- final source = displayableSources[index];
236- return CheckboxListTile (
237- title: Text (source.name, style: textTheme.titleMedium),
238- value: filterState.selectedSources.contains (source),
239- onChanged: (bool ? value) {
240- if (value != null ) {
241- context.read <HeadlinesFilterBloc >().add (
242- FilterSourceToggled (source: source, isSelected: value),
243- );
244- }
245- },
246- controlAffinity: ListTileControlAffinity .leading,
247- contentPadding: const EdgeInsets .symmetric (
248- horizontal: AppSpacing .paddingMedium,
249- ),
250- );
251- },
252- );
253- }
254249}
0 commit comments