Skip to content

Commit 40546b2

Browse files
committed
feat(headlines): improve category filter UI
- Apply theme to text styles - Use outlined icons - Improve loading indicator - Add consistent paddings
1 parent 13f278d commit 40546b2

File tree

1 file changed

+51
-39
lines changed

1 file changed

+51
-39
lines changed

lib/headlines-feed/view/category_filter_page.dart

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,17 @@ class _CategoryFilterPageState extends State<CategoryFilterPage> {
100100
Widget build(BuildContext context) {
101101
final l10n = context.l10n;
102102

103+
final theme = Theme.of(context); // Get theme
104+
final textTheme = theme.textTheme; // Get textTheme
105+
final colorScheme = theme.colorScheme; // Get colorScheme
106+
103107
return Scaffold(
104108
appBar: AppBar(
105-
// Default back button will pop without result (cancelling)
106-
title: Text(l10n.headlinesFeedFilterCategoryLabel),
109+
title: Text(
110+
l10n.headlinesFeedFilterCategoryLabel,
111+
style: textTheme.titleLarge, // Apply consistent title style
112+
),
107113
actions: [
108-
// Apply Button
109114
IconButton(
110115
icon: const Icon(Icons.check),
111116
tooltip: l10n.headlinesFeedFilterApplyButton,
@@ -129,6 +134,9 @@ class _CategoryFilterPageState extends State<CategoryFilterPage> {
129134
/// Builds the main content body based on the current [CategoriesFilterState].
130135
Widget _buildBody(BuildContext context, CategoriesFilterState state) {
131136
final l10n = context.l10n;
137+
final theme = Theme.of(context); // Get theme
138+
final textTheme = theme.textTheme; // Get textTheme
139+
final colorScheme = theme.colorScheme; // Get colorScheme
132140

133141
// Handle initial loading state
134142
if (state.status == CategoriesFilterStatus.loading) {
@@ -140,53 +148,44 @@ class _CategoryFilterPageState extends State<CategoryFilterPage> {
140148
}
141149

142150
// Handle failure state (show error and retry button)
143-
// Only show full error screen if not loading more (i.e., initial load failed)
144151
if (state.status == CategoriesFilterStatus.failure &&
145152
state.categories.isEmpty) {
146153
return FailureStateWidget(
147154
message: state.error?.toString() ?? l10n.unknownError,
148-
onRetry:
149-
() => context.read<CategoriesFilterBloc>().add(
150-
CategoriesFilterRequested(),
151-
),
155+
onRetry: () =>
156+
context.read<CategoriesFilterBloc>().add(CategoriesFilterRequested()),
152157
);
153158
}
154159

155160
// Handle empty state (after successful load but no categories found)
156161
if (state.status == CategoriesFilterStatus.success &&
157162
state.categories.isEmpty) {
158163
return InitialStateWidget(
159-
icon: Icons.search_off,
164+
icon: Icons.search_off_outlined, // Use outlined version
160165
headline: l10n.categoryFilterEmptyHeadline,
161166
subheadline: l10n.categoryFilterEmptySubheadline,
162167
);
163168
}
164169

165170
// Handle loaded state (success or loading more)
166-
// Show the list, potentially with a loading indicator at the bottom
167171
return ListView.builder(
168172
controller: _scrollController,
169-
padding: const EdgeInsets.only(
170-
bottom: AppSpacing.xxl, // Padding at the bottom for loader/content
171-
),
172-
// Add 1 to item count if loading more or if failed during load more
173-
itemCount:
174-
state.categories.length +
173+
padding: const EdgeInsets.symmetric(vertical: AppSpacing.paddingSmall)
174+
.copyWith(bottom: AppSpacing.xxl), // Consistent vertical padding
175+
itemCount: state.categories.length +
175176
((state.status == CategoriesFilterStatus.loadingMore ||
176177
(state.status == CategoriesFilterStatus.failure &&
177178
state.categories.isNotEmpty))
178179
? 1
179180
: 0),
180181
itemBuilder: (context, index) {
181-
// Check if we need to render the loading/error indicator at the end
182182
if (index >= state.categories.length) {
183183
if (state.status == CategoriesFilterStatus.loadingMore) {
184184
return const Padding(
185185
padding: EdgeInsets.symmetric(vertical: AppSpacing.lg),
186186
child: Center(child: CircularProgressIndicator()),
187187
);
188188
} else if (state.status == CategoriesFilterStatus.failure) {
189-
// Show a smaller error indicator at the bottom if load more failed
190189
return Padding(
191190
padding: const EdgeInsets.symmetric(
192191
vertical: AppSpacing.md,
@@ -195,51 +194,64 @@ class _CategoryFilterPageState extends State<CategoryFilterPage> {
195194
child: Center(
196195
child: Text(
197196
l10n.loadMoreError,
198-
style: Theme.of(context).textTheme.bodySmall?.copyWith(
199-
color: Theme.of(context).colorScheme.error,
200-
),
197+
style: textTheme.bodySmall
198+
?.copyWith(color: colorScheme.error),
201199
),
202200
),
203201
);
204-
} else {
205-
return const SizedBox.shrink(); // Should not happen if hasMore is false
206202
}
203+
return const SizedBox.shrink();
207204
}
208205

209-
// Render the actual category item
210206
final category = state.categories[index];
211207
final isSelected = _pageSelectedCategories.contains(category);
212208

213209
return CheckboxListTile(
214-
title: Text(category.name),
215-
secondary:
216-
category.iconUrl != null
217-
? SizedBox(
218-
width: 40,
219-
height: 40,
210+
title: Text(category.name, style: textTheme.titleMedium),
211+
secondary: category.iconUrl != null
212+
? SizedBox(
213+
width: AppSpacing.xl + AppSpacing.sm, // 40 -> 32
214+
height: AppSpacing.xl + AppSpacing.sm,
215+
child: ClipRRect(
216+
borderRadius: BorderRadius.circular(AppSpacing.xs),
220217
child: Image.network(
221218
category.iconUrl!,
222219
fit: BoxFit.contain,
223-
errorBuilder:
224-
(context, error, stackTrace) =>
225-
const Icon(Icons.category), // Placeholder icon
220+
errorBuilder: (context, error, stackTrace) => Icon(
221+
Icons.category_outlined, // Use outlined
222+
color: colorScheme.onSurfaceVariant, // Theme color
223+
size: AppSpacing.xl,
224+
),
225+
loadingBuilder: (context, child, loadingProgress) {
226+
if (loadingProgress == null) return child;
227+
return Center(
228+
child: CircularProgressIndicator(
229+
strokeWidth: 2,
230+
value: loadingProgress.expectedTotalBytes != null
231+
? loadingProgress.cumulativeBytesLoaded /
232+
loadingProgress.expectedTotalBytes!
233+
: null,
234+
),
235+
);
236+
},
226237
),
227-
)
228-
: null,
238+
),
239+
)
240+
: null,
229241
value: isSelected,
230242
onChanged: (bool? value) {
231-
// When a checkbox state changes, update the local selection set
232-
// (`_pageSelectedCategories`) for this page.
233243
setState(() {
234244
if (value == true) {
235-
// Add the category if checked.
236245
_pageSelectedCategories.add(category);
237246
} else {
238-
// Remove the category if unchecked.
239247
_pageSelectedCategories.remove(category);
240248
}
241249
});
242250
},
251+
controlAffinity: ListTileControlAffinity.leading,
252+
contentPadding: const EdgeInsets.symmetric(
253+
horizontal: AppSpacing.paddingMedium, // Standard padding
254+
),
243255
);
244256
},
245257
);

0 commit comments

Comments
 (0)