Skip to content

Commit 8d4706b

Browse files
committed
feat(search): improve headlines search UI
- Use shared state widgets - Integrate search bar in AppBar - Add search button to AppBar
1 parent 822dcce commit 8d4706b

File tree

8 files changed

+125
-47
lines changed

8 files changed

+125
-47
lines changed

lib/headlines-feed/view/headlines_feed_page.dart

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,17 @@ class _HeadlinesFeedViewState extends State<_HeadlinesFeedView> {
116116
builder: (context, state) {
117117
switch (state) {
118118
case HeadlinesFeedLoading():
119-
return const LoadingStateWidget();
119+
return const LoadingStateWidget(
120+
icon: Icons.hourglass_empty,
121+
headline: 'Loading...',
122+
subheadline: 'Fetching headlines',
123+
);
120124
case HeadlinesFeedLoaded():
121125
return RefreshIndicator(
122126
onRefresh: () async {
123-
context
124-
.read<HeadlinesFeedBloc>()
125-
.add(HeadlinesFeedRefreshRequested());
127+
context.read<HeadlinesFeedBloc>().add(
128+
HeadlinesFeedRefreshRequested(),
129+
);
126130
},
127131
child: ListView.builder(
128132
controller: _scrollController,
@@ -131,7 +135,11 @@ class _HeadlinesFeedViewState extends State<_HeadlinesFeedView> {
131135
: state.headlines.length,
132136
itemBuilder: (context, index) {
133137
if (index >= state.headlines.length) {
134-
return const LoadingStateWidget();
138+
return const LoadingStateWidget(
139+
icon: Icons.hourglass_empty,
140+
headline: 'Loading...',
141+
subheadline: 'Fetching more headlines',
142+
);
135143
}
136144
final headline = state.headlines[index];
137145
return HeadlineItemWidget(headline: headline);
Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_bloc/flutter_bloc.dart';
3+
import 'package:ht_headlines_repository/ht_headlines_repository.dart';
34
import 'package:ht_main/headlines-feed/widgets/headline_item_widget.dart';
45
import 'package:ht_main/headlines-search/bloc/headlines_search_bloc.dart';
6+
import 'package:ht_main/shared/widgets/failure_state_widget.dart';
7+
import 'package:ht_main/shared/widgets/initial_state_widget.dart';
8+
import 'package:ht_main/shared/widgets/loading_state_widget.dart';
59

610
class HeadlinesSearchView extends StatelessWidget {
711
const HeadlinesSearchView({super.key});
@@ -10,47 +14,69 @@ class HeadlinesSearchView extends StatelessWidget {
1014
Widget build(BuildContext context) {
1115
return Scaffold(
1216
appBar: AppBar(
13-
title: SearchBar(
14-
hintText: 'Search Headlines',
17+
title: TextField(
18+
decoration: const InputDecoration(
19+
hintText: 'Search Headlines...',
20+
),
1521
onChanged: (value) {
16-
context
17-
.read<HeadlinesSearchBloc>()
18-
.add(HeadlinesSearchTermChanged(searchTerm: value));
19-
},
20-
onSubmitted: (value) {
21-
context.read<HeadlinesSearchBloc>().add(HeadlinesSearchRequested());
22+
context.read<HeadlinesSearchBloc>().add(
23+
HeadlinesSearchTermChanged(searchTerm: value),
24+
);
2225
},
2326
),
27+
actions: [
28+
IconButton(
29+
icon: const Icon(Icons.search),
30+
onPressed: () {
31+
context.read<HeadlinesSearchBloc>().add(
32+
HeadlinesSearchRequested(),
33+
);
34+
},
35+
),
36+
],
2437
),
2538
body: BlocBuilder<HeadlinesSearchBloc, HeadlinesSearchState>(
2639
builder: (context, state) {
27-
if (state is HeadlinesSearchInitial) {
28-
return const Center(
29-
child: Column(
30-
mainAxisAlignment: MainAxisAlignment.center,
31-
children: [
32-
Icon(Icons.search, size: 64),
33-
SizedBox(height: 16),
34-
Text('Search Headlines', style: TextStyle(fontSize: 24)),
35-
Text('Enter keywords to find articles'),
36-
],
40+
return switch (state) {
41+
HeadlinesSearchInitial() => const InitialStateWidget(
42+
icon: Icons.search,
43+
headline: 'Search Headlines',
44+
subheadline: 'Enter keywords to find articles',
45+
),
46+
HeadlinesSearchLoading() => const LoadingStateWidget(
47+
icon: Icons.hourglass_empty,
48+
headline: 'Loading...',
49+
subheadline: 'Fetching headlines',
50+
),
51+
HeadlinesSearchLoaded(:final headlines) =>
52+
_HeadlinesSearchLoadedView(headlines: headlines),
53+
HeadlinesSearchError(:final message) => FailureStateWidget(
54+
message: message,
55+
onRetry: () {
56+
context
57+
.read<HeadlinesSearchBloc>()
58+
.add(HeadlinesSearchRequested());
59+
},
3760
),
38-
);
39-
} else if (state is HeadlinesSearchLoading) {
40-
return const Center(child: CircularProgressIndicator());
41-
} else if (state is HeadlinesSearchLoaded) {
42-
return ListView.builder(
43-
itemCount: state.headlines.length,
44-
itemBuilder: (context, index) {
45-
return HeadlineItemWidget(headline: state.headlines[index]);
46-
},
47-
);
48-
} else if (state is HeadlinesSearchError) {
49-
return Center(child: Text(state.message));
50-
}
51-
return Container(); // Should never reach here
61+
};
5262
},
5363
),
5464
);
5565
}
5666
}
67+
68+
class _HeadlinesSearchLoadedView extends StatelessWidget {
69+
const _HeadlinesSearchLoadedView({required this.headlines});
70+
71+
final List<Headline> headlines;
72+
73+
@override
74+
Widget build(BuildContext context) {
75+
return ListView.builder(
76+
itemCount: headlines.length,
77+
itemBuilder: (context, index) {
78+
return HeadlineItemWidget(headline: headlines[index]);
79+
},
80+
);
81+
}
82+
}
Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
11
import 'package:flutter/material.dart';
22

33
class InitialStateWidget extends StatelessWidget {
4-
const InitialStateWidget({super.key});
4+
const InitialStateWidget({
5+
required this.icon,
6+
required this.headline,
7+
required this.subheadline,
8+
super.key,
9+
});
10+
11+
final IconData icon;
12+
final String headline;
13+
final String subheadline;
514

615
@override
716
Widget build(BuildContext context) {
8-
return const Center(
9-
child: Text('Initial State'),
17+
return Center(
18+
child: Column(
19+
mainAxisAlignment: MainAxisAlignment.center,
20+
children: [
21+
Icon(icon, size: 64),
22+
const SizedBox(height: 16),
23+
Text(headline, style: const TextStyle(fontSize: 24)),
24+
Text(subheadline),
25+
],
26+
),
1027
);
1128
}
1229
}

lib/shared/widgets/loading_state_widget.dart

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,32 @@
11
import 'package:flutter/material.dart';
22

33
class LoadingStateWidget extends StatelessWidget {
4-
const LoadingStateWidget({super.key});
4+
const LoadingStateWidget({
5+
required this.icon,
6+
required this.headline,
7+
required this.subheadline,
8+
super.key,
9+
});
10+
11+
final IconData icon;
12+
final String headline;
13+
final String subheadline;
514

615
@override
716
Widget build(BuildContext context) {
817
return Center(
9-
child: CircularProgressIndicator(
10-
color: Theme.of(context).colorScheme.secondary,
18+
child: Column(
19+
mainAxisAlignment: MainAxisAlignment.center,
20+
children: [
21+
Icon(icon, size: 64),
22+
const SizedBox(height: 16),
23+
Text(headline, style: const TextStyle(fontSize: 24)),
24+
Text(subheadline),
25+
const SizedBox(height: 16),
26+
CircularProgressIndicator(
27+
color: Theme.of(context).colorScheme.secondary,
28+
),
29+
],
1130
),
1231
);
1332
}

memory-bank/.clinerules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@
2323
- Document exceptions associated with calling a function in its documentation comments.
2424
- Define descriptive exceptions by implementing `Exception` with descriptive names, rather than throwing generic `Exception`.
2525
- When using `go_router` for navigation, `BuildContext` is required for methods like `context.goNamed()`. Since BLoCs don't have inherent access to `BuildContext`, it needs to be passed through the event triggering the navigation.
26+
27+
## Repository
28+
- The `searchHeadlines` method in `HtHeadlinesRepository` does not support pagination. Pagination-like behavior is handled in the `HeadlinesSearchBloc`.

memory-bank/activeContext.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
## Current Focus
44

5-
Creating the project README.
5+
Implementing remaining features.
66

77
## Recent Changes
88

9-
- Updated the memory bank to reflect the full product vision and licensing details.
9+
- Updated the search page to use shared widgets for initial, loading, and error states.
10+
- Simplified the search bar by integrating it directly into the AppBar.
11+
- Added a search button to the AppBar to trigger the search.
1012

1113
## Next Steps
1214

memory-bank/progress.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
- `HeadlinesSearchBloc` for managing headlines search.
2323
- `HeadlinesSearchPage` and `HeadlinesSearchView` for displaying search results.
2424
- Debounced search term input.
25+
- Uses shared widgets for initial, loading, and error states.
26+
- Integrated search bar directly into the AppBar.
27+
- Search button in the AppBar to trigger the search.
2528
- Error handling within BLoCs, with specific error states.
2629
- Page/View pattern for UI components.
2730

@@ -43,7 +46,7 @@
4346

4447
## Current Status
4548

46-
- Early development stage. Core architecture, basic headline feed and search functionality, and initial memory bank documentation are complete. The focus is now on creating the README and then implementing the remaining features and backend integration.
49+
- Implementing remaining features and backend integration.
4750

4851
## Known Issues
4952

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: ht_main
22
description: main headlines toolkit mobile app.
3-
version: 0.17.0
3+
version: 0.19.0
44
publish_to: none
55
repository: https://github.com/headlines-toolkit/ht-main
66
environment:

0 commit comments

Comments
 (0)