Skip to content

Commit eb02682

Browse files
committed
refactor(feed): improve SourceListFilterPage UI
Refactors the `SourceListFilterPage` to provide a more scalable and user-friendly interface. - Replaces the horizontal `ChoiceChip` list for countries with a `ListTile` that navigates to the new generic `MultiSelectSearchPage`. This provides a much better UX for long lists. - Replaces the horizontal `ChoiceChip` list for source types with a vertical list of `CheckboxListTile` widgets, making all options immediately visible and accessible. - Deletes the now-obsolete `_buildCountryCapsules` and `_buildSourceTypeCapsules` methods.
1 parent 5b3a4fe commit eb02682

File tree

1 file changed

+43
-113
lines changed

1 file changed

+43
-113
lines changed

lib/headlines-feed/view/source_list_filter_page.dart

Lines changed: 43 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import 'package:core/core.dart';
44
import 'package:flutter/material.dart';
55
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/app_localizations.dart';
66
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart';
7+
import 'package:flutter_news_app_mobile_client_full_source_code/router/routes.dart';
8+
import 'package:flutter_news_app_mobile_client_full_source_code/shared/widgets/multi_select_search_page.dart';
9+
import 'package:go_router/go_router.dart';
710
import 'package:ui_kit/ui_kit.dart';
811

912
/// {@template source_list_filter_page}
@@ -83,20 +86,51 @@ class _SourceListFilterPageState extends State<SourceListFilterPage> {
8386
padding: const EdgeInsets.symmetric(vertical: AppSpacing.md),
8487
children: [
8588
// Section for filtering by headquarters country.
86-
_buildSectionHeader(
87-
context,
88-
l10n.headlinesFeedFilterSourceCountryLabel,
89+
ListTile(
90+
title: Text(l10n.headlinesFeedFilterSourceCountryLabel),
91+
subtitle: Text(
92+
_selectedHeadquarterCountries.isEmpty
93+
? l10n.headlinesFeedFilterAllLabel
94+
: l10n.headlinesFeedFilterSelectedCountLabel(
95+
_selectedHeadquarterCountries.length,
96+
),
97+
),
98+
trailing: const Icon(Icons.chevron_right),
99+
onTap: () async {
100+
final result = await context.pushNamed<Set<Country>>(
101+
Routes.multiSelectSearchName,
102+
extra: {
103+
'title': l10n.headlinesFeedFilterSourceCountryLabel,
104+
'allItems': widget.allCountries,
105+
'initialSelectedItems': _selectedHeadquarterCountries,
106+
'itemBuilder': (Country country) => country.name,
107+
},
108+
);
109+
110+
if (result != null && mounted) {
111+
setState(() => _selectedHeadquarterCountries = result);
112+
}
113+
},
89114
),
90-
_buildCountryCapsules(context, widget.allCountries, l10n, textTheme),
115+
const Divider(height: 1),
91116
const SizedBox(height: AppSpacing.lg),
92117

93118
// Section for filtering by source type.
94119
_buildSectionHeader(context, l10n.headlinesFeedFilterSourceTypeLabel),
95-
_buildSourceTypeCapsules(
96-
context,
97-
widget.allSourceTypes,
98-
l10n,
99-
textTheme,
120+
...widget.allSourceTypes.map(
121+
(sourceType) => CheckboxListTile(
122+
title: Text(sourceType.name),
123+
value: _selectedSourceTypes.contains(sourceType),
124+
onChanged: (isSelected) {
125+
setState(() {
126+
if (isSelected == true) {
127+
_selectedSourceTypes.add(sourceType);
128+
} else {
129+
_selectedSourceTypes.remove(sourceType);
130+
}
131+
});
132+
},
133+
),
100134
),
101135
],
102136
),
@@ -115,108 +149,4 @@ class _SourceListFilterPageState extends State<SourceListFilterPage> {
115149
),
116150
);
117151
}
118-
119-
/// Builds the horizontal list of [ChoiceChip] widgets for countries.
120-
Widget _buildCountryCapsules(
121-
BuildContext context,
122-
List<Country> allCountries,
123-
AppLocalizations l10n,
124-
TextTheme textTheme,
125-
) {
126-
return SizedBox(
127-
height: AppSpacing.xl + AppSpacing.md,
128-
child: ListView.builder(
129-
scrollDirection: Axis.horizontal,
130-
padding: const EdgeInsets.symmetric(
131-
horizontal: AppSpacing.paddingMedium,
132-
vertical: AppSpacing.sm,
133-
),
134-
itemCount: allCountries.length + 1,
135-
itemBuilder: (context, index) {
136-
if (index == 0) {
137-
// The 'All' chip.
138-
return ChoiceChip(
139-
label: Text(l10n.headlinesFeedFilterAllLabel),
140-
labelStyle: textTheme.labelLarge,
141-
selected: _selectedHeadquarterCountries.isEmpty,
142-
onSelected: (_) => setState(_selectedHeadquarterCountries.clear),
143-
);
144-
}
145-
final country = allCountries[index - 1];
146-
return Padding(
147-
padding: const EdgeInsets.only(left: AppSpacing.sm),
148-
child: ChoiceChip(
149-
avatar: country.flagUrl.isNotEmpty
150-
? CircleAvatar(
151-
backgroundImage: NetworkImage(country.flagUrl),
152-
radius: AppSpacing.sm + AppSpacing.xs,
153-
)
154-
: null,
155-
label: Text(country.name),
156-
labelStyle: textTheme.labelLarge,
157-
selected: _selectedHeadquarterCountries.contains(country),
158-
onSelected: (isSelected) {
159-
setState(() {
160-
if (isSelected) {
161-
_selectedHeadquarterCountries.add(country);
162-
} else {
163-
_selectedHeadquarterCountries.remove(country);
164-
}
165-
});
166-
},
167-
),
168-
);
169-
},
170-
),
171-
);
172-
}
173-
174-
/// Builds the horizontal list of [ChoiceChip] widgets for source types.
175-
Widget _buildSourceTypeCapsules(
176-
BuildContext context,
177-
List<SourceType> allSourceTypes,
178-
AppLocalizations l10n,
179-
TextTheme textTheme,
180-
) {
181-
return SizedBox(
182-
height: AppSpacing.xl + AppSpacing.md,
183-
child: ListView.builder(
184-
scrollDirection: Axis.horizontal,
185-
padding: const EdgeInsets.symmetric(
186-
horizontal: AppSpacing.paddingMedium,
187-
vertical: AppSpacing.sm,
188-
),
189-
itemCount: allSourceTypes.length + 1,
190-
itemBuilder: (context, index) {
191-
if (index == 0) {
192-
// The 'All' chip.
193-
return ChoiceChip(
194-
label: Text(l10n.headlinesFeedFilterAllLabel),
195-
labelStyle: textTheme.labelLarge,
196-
selected: _selectedSourceTypes.isEmpty,
197-
onSelected: (_) => setState(_selectedSourceTypes.clear),
198-
);
199-
}
200-
final sourceType = allSourceTypes[index - 1];
201-
return Padding(
202-
padding: const EdgeInsets.only(left: AppSpacing.sm),
203-
child: ChoiceChip(
204-
label: Text(sourceType.name),
205-
labelStyle: textTheme.labelLarge,
206-
selected: _selectedSourceTypes.contains(sourceType),
207-
onSelected: (isSelected) {
208-
setState(() {
209-
if (isSelected) {
210-
_selectedSourceTypes.add(sourceType);
211-
} else {
212-
_selectedSourceTypes.remove(sourceType);
213-
}
214-
});
215-
},
216-
),
217-
);
218-
},
219-
),
220-
);
221-
}
222152
}

0 commit comments

Comments
 (0)