@@ -7,8 +7,11 @@ import 'package:ht_main/headlines-feed/widgets/headline_item_widget.dart';
7
7
import 'package:ht_main/l10n/l10n.dart' ;
8
8
import 'package:ht_main/router/routes.dart' ;
9
9
import 'package:ht_main/shared/constants/constants.dart' ;
10
+ import 'package:ht_categories_client/ht_categories_client.dart' ; // Import Category
11
+ import 'package:ht_countries_client/ht_countries_client.dart' ; // Import Country
10
12
import 'package:ht_main/shared/widgets/failure_state_widget.dart' ;
11
13
import 'package:ht_main/shared/widgets/loading_state_widget.dart' ;
14
+ import 'package:ht_sources_client/ht_sources_client.dart' ; // Import Source
12
15
13
16
class HeadlinesFeedPage extends StatelessWidget {
14
17
const HeadlinesFeedPage ({super .key});
@@ -93,10 +96,11 @@ class _HeadlinesFeedViewState extends State<_HeadlinesFeedView> {
93
96
builder: (context, state) {
94
97
var isFilterApplied = false ;
95
98
if (state is HeadlinesFeedLoaded ) {
99
+ // Check if any filter list is non-null and not empty
96
100
isFilterApplied =
97
- state.filter.category != null ||
98
- state.filter.source != null ||
99
- state.filter.eventCountry != null ;
101
+ ( state.filter.categories ? .isNotEmpty ?? false ) ||
102
+ ( state.filter.sources ? .isNotEmpty ?? false ) ||
103
+ ( state.filter.eventCountries ? .isNotEmpty ?? false ) ;
100
104
}
101
105
return Stack (
102
106
children: [
@@ -210,18 +214,20 @@ class _HeadlinesFilterBottomSheet extends StatefulWidget {
210
214
211
215
class _HeadlinesFilterBottomSheetState
212
216
extends State <_HeadlinesFilterBottomSheet > {
213
- String ? selectedCategory;
214
- String ? selectedSource;
215
- String ? selectedEventCountry;
217
+ // Use lists to store selected filters
218
+ List <Category > selectedCategories = [];
219
+ List <Source > selectedSources = [];
220
+ List <Country > selectedEventCountries = [];
216
221
217
222
@override
218
223
void initState () {
219
224
super .initState ();
220
225
final state = widget.bloc.state;
221
226
if (state is HeadlinesFeedLoaded ) {
222
- selectedCategory = state.filter.category;
223
- selectedSource = state.filter.source;
224
- selectedEventCountry = state.filter.eventCountry;
227
+ // Initialize lists from the current filter state, handle nulls
228
+ selectedCategories = List .from (state.filter.categories ?? []);
229
+ selectedSources = List .from (state.filter.sources ?? []);
230
+ selectedEventCountries = List .from (state.filter.eventCountries ?? []);
225
231
}
226
232
}
227
233
@@ -247,104 +253,92 @@ class _HeadlinesFilterBottomSheetState
247
253
textAlign: TextAlign .center,
248
254
),
249
255
const SizedBox (height: AppSpacing .xl),
250
- // Category Dropdown
251
- DropdownButtonFormField <String >(
252
- decoration: InputDecoration (
253
- labelText: l10n.headlinesFeedFilterCategoryLabel,
254
- ),
255
- value: selectedCategory,
256
- // TODO(fulleni): Populate items dynamically from repository/config
257
- items: [
258
- DropdownMenuItem <String >(
259
- child: Text (l10n.headlinesFeedFilterAllOption),
260
- ),
261
- DropdownMenuItem (
262
- value: 'technology' ,
263
- child: Text (l10n.headlinesFeedFilterCategoryTechnology),
264
- ),
265
- DropdownMenuItem (
266
- value: 'business' ,
267
- child: Text (l10n.headlinesFeedFilterCategoryBusiness),
268
- ),
269
- DropdownMenuItem (
270
- value: 'sports' ,
271
- child: Text (l10n.headlinesFeedFilterCategorySports),
256
+
257
+ // --- Category Filters ---
258
+ Text (
259
+ l10n.headlinesFeedFilterCategoryLabel,
260
+ style: Theme .of (context).textTheme.titleMedium,
261
+ ),
262
+ const SizedBox (height: AppSpacing .md),
263
+ // TODO(cline): Implement multi-select UI for categories
264
+ // Fetch available categories from a repository
265
+ // Use Wrap + FilterChip to display options
266
+ // Update selectedCategories list in setState when chips are toggled
267
+ // Example placeholder:
268
+ Container (
269
+ padding: const EdgeInsets .all (AppSpacing .md),
270
+ decoration: BoxDecoration (
271
+ border: Border .all (
272
+ color: Theme .of (context).colorScheme.outline,
272
273
),
273
- ],
274
- onChanged: (value) {
275
- setState (() {
276
- selectedCategory = value;
277
- });
278
- },
274
+ borderRadius: BorderRadius .circular (AppSpacing .sm),
275
+ ),
276
+ child: const Text ('Category FilterChip UI Placeholder' ),
279
277
),
280
278
const SizedBox (height: AppSpacing .lg),
281
- // Source Dropdown
282
- DropdownButtonFormField <String >(
283
- decoration: InputDecoration (
284
- labelText: l10n.headlinesFeedFilterSourceLabel,
285
- ),
286
- value: selectedSource,
287
- // TODO(fulleni): Populate items dynamically
288
- items: [
289
- DropdownMenuItem <String >(
290
- child: Text (l10n.headlinesFeedFilterAllOption),
291
- ),
292
- DropdownMenuItem (
293
- value: 'cnn' ,
294
- child: Text (l10n.headlinesFeedFilterSourceCNN),
295
- ),
296
- DropdownMenuItem (
297
- value: 'reuters' ,
298
- child: Text (l10n.headlinesFeedFilterSourceReuters),
279
+
280
+ // --- Source Filters ---
281
+ Text (
282
+ l10n.headlinesFeedFilterSourceLabel,
283
+ style: Theme .of (context).textTheme.titleMedium,
284
+ ),
285
+ const SizedBox (height: AppSpacing .md),
286
+ // TODO(cline): Implement multi-select UI for sources
287
+ // Fetch available sources from a repository
288
+ // Use Wrap + FilterChip to display options
289
+ // Update selectedSources list in setState when chips are toggled
290
+ // Example placeholder:
291
+ Container (
292
+ padding: const EdgeInsets .all (AppSpacing .md),
293
+ decoration: BoxDecoration (
294
+ border: Border .all (
295
+ color: Theme .of (context).colorScheme.outline,
299
296
),
300
- ],
301
- onChanged: (value) {
302
- setState (() {
303
- selectedSource = value;
304
- });
305
- },
297
+ borderRadius: BorderRadius .circular (AppSpacing .sm),
298
+ ),
299
+ child: const Text ('Source FilterChip UI Placeholder' ),
306
300
),
307
301
const SizedBox (height: AppSpacing .lg),
308
- // Event Country Dropdown
309
- DropdownButtonFormField <String >(
310
- decoration: InputDecoration (
311
- labelText: l10n.headlinesFeedFilterEventCountryLabel,
312
- ),
313
- value: selectedEventCountry,
314
- // TODO(fulleni): Populate items dynamically
315
- items: [
316
- DropdownMenuItem <String >(
317
- child: Text (l10n.headlinesFeedFilterAllOption),
318
- ),
319
- DropdownMenuItem (
320
- value: 'US' ,
321
- child: Text (l10n.headlinesFeedFilterCountryUS),
322
- ),
323
- DropdownMenuItem (
324
- value: 'UK' ,
325
- child: Text (l10n.headlinesFeedFilterCountryUK),
326
- ),
327
- DropdownMenuItem (
328
- value: 'CA' ,
329
- child: Text (l10n.headlinesFeedFilterCountryCA),
302
+
303
+ // --- Event Country Filters ---
304
+ Text (
305
+ l10n.headlinesFeedFilterEventCountryLabel,
306
+ style: Theme .of (context).textTheme.titleMedium,
307
+ ),
308
+ const SizedBox (height: AppSpacing .md),
309
+ // TODO(cline): Implement multi-select UI for event countries
310
+ // Fetch available countries from a repository
311
+ // Use Wrap + FilterChip to display options
312
+ // Update selectedEventCountries list in setState when chips are toggled
313
+ // Example placeholder:
314
+ Container (
315
+ padding: const EdgeInsets .all (AppSpacing .md),
316
+ decoration: BoxDecoration (
317
+ border: Border .all (
318
+ color: Theme .of (context).colorScheme.outline,
330
319
),
331
- ],
332
- onChanged: (value) {
333
- setState (() {
334
- selectedEventCountry = value;
335
- });
336
- },
320
+ borderRadius: BorderRadius .circular (AppSpacing .sm),
321
+ ),
322
+ child: const Text ('Country FilterChip UI Placeholder' ),
337
323
),
338
324
const SizedBox (height: AppSpacing .xl),
339
- // Use FilledButton for primary action
325
+
326
+ // --- Action Buttons ---
340
327
FilledButton (
341
328
onPressed: () {
342
329
widget.bloc.add (
343
330
HeadlinesFeedFilterChanged (
344
- // Pass null if 'All' was selected (value is null now)
345
- category: selectedCategory,
346
- source: selectedSource,
347
- eventCountry: selectedEventCountry,
331
+ // Pass the selected lists
332
+ categories:
333
+ selectedCategories.isNotEmpty
334
+ ? selectedCategories
335
+ : null ,
336
+ sources:
337
+ selectedSources.isNotEmpty ? selectedSources : null ,
338
+ eventCountries:
339
+ selectedEventCountries.isNotEmpty
340
+ ? selectedEventCountries
341
+ : null ,
348
342
),
349
343
);
350
344
Navigator .pop (context);
@@ -354,12 +348,20 @@ class _HeadlinesFilterBottomSheetState
354
348
const SizedBox (height: AppSpacing .sm),
355
349
TextButton (
356
350
onPressed: () {
351
+ // Clear local state lists
357
352
setState (() {
358
- selectedCategory = null ;
359
- selectedSource = null ;
360
- selectedEventCountry = null ;
353
+ selectedCategories. clear () ;
354
+ selectedSources. clear () ;
355
+ selectedEventCountries. clear () ;
361
356
});
362
- widget.bloc.add (const HeadlinesFeedFilterChanged ());
357
+ // Dispatch event with null lists to clear filters in BLoC
358
+ widget.bloc.add (
359
+ const HeadlinesFeedFilterChanged (
360
+ categories: null ,
361
+ sources: null ,
362
+ eventCountries: null ,
363
+ ),
364
+ );
363
365
Navigator .pop (context);
364
366
},
365
367
child: Text (l10n.headlinesFeedFilterResetButton),
0 commit comments