Skip to content

Commit 39e76b6

Browse files
committed
feat: Implement HeadlinesFeedBloc
- Added HeadlinesFeedBloc - Manages headlines feed state - Fetches headlines from repository
1 parent 5ee94d0 commit 39e76b6

File tree

6 files changed

+213
-2
lines changed

6 files changed

+213
-2
lines changed

analysis_options.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
analyzer:
2+
errors:
3+
avoid_catches_without_on_clauses: ignore
14
include: package:very_good_analysis/analysis_options.7.0.0.yaml
25
linter:
36
rules:
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import 'package:bloc/bloc.dart';
2+
import 'package:bloc_concurrency/bloc_concurrency.dart';
3+
import 'package:equatable/equatable.dart';
4+
import 'package:ht_headlines_repository/ht_headlines_repository.dart';
5+
6+
part 'headlines_feed_event.dart';
7+
part 'headlines_feed_state.dart';
8+
9+
/// {@template headlines_feed_bloc}
10+
/// A Bloc that manages the headlines feed.
11+
///
12+
/// It handles fetching and refreshing headlines data using the
13+
/// [HtHeadlinesRepository].
14+
/// {@endtemplate}
15+
class HeadlinesFeedBloc extends Bloc<HeadlinesFeedEvent, HeadlinesFeedState> {
16+
/// {@macro headlines_feed_bloc}
17+
HeadlinesFeedBloc({required HtHeadlinesRepository headlinesRepository})
18+
: _headlinesRepository = headlinesRepository,
19+
super(HeadlinesFeedInitial()) {
20+
on<HeadlinesFeedFetchRequested>(
21+
_onHeadlinesFeedFetchRequested,
22+
transformer: sequential(),
23+
);
24+
on<HeadlinesFeedRefreshRequested>(
25+
_onHeadlinesFeedRefreshRequested,
26+
transformer: restartable(),
27+
);
28+
}
29+
30+
final HtHeadlinesRepository _headlinesRepository;
31+
32+
/// Handles [HeadlinesFeedFetchRequested] events.
33+
///
34+
/// Fetches headlines from the repository and emits
35+
/// [HeadlinesFeedLoading], and either [HeadlinesFeedLoaded] or
36+
/// [HeadlinesFeedError] states.
37+
Future<void> _onHeadlinesFeedFetchRequested(
38+
HeadlinesFeedFetchRequested event,
39+
Emitter<HeadlinesFeedState> emit,
40+
) async {
41+
if (state is HeadlinesFeedLoaded &&
42+
(state as HeadlinesFeedLoaded).hasMore) {
43+
final currentState = state as HeadlinesFeedLoaded;
44+
emit(HeadlinesFeedLoading());
45+
try {
46+
final response = await _headlinesRepository.getHeadlines(
47+
limit: 20,
48+
startAfterId: currentState.cursor,
49+
);
50+
emit(
51+
HeadlinesFeedLoaded(
52+
headlines: currentState.headlines + response.items,
53+
hasMore: response.hasMore,
54+
cursor: response.cursor,
55+
),
56+
);
57+
} on HeadlinesFetchException catch (e) {
58+
emit(HeadlinesFeedError(message: e.message));
59+
} catch (_) {
60+
emit(const HeadlinesFeedError(message: 'An unexpected error occurred'));
61+
}
62+
} else {
63+
emit(HeadlinesFeedLoading());
64+
try {
65+
final response = await _headlinesRepository.getHeadlines(limit: 20);
66+
emit(
67+
HeadlinesFeedLoaded(
68+
headlines: response.items,
69+
hasMore: response.hasMore,
70+
cursor: response.cursor,
71+
),
72+
);
73+
} on HeadlinesFetchException catch (e) {
74+
emit(HeadlinesFeedError(message: e.message));
75+
} catch (_) {
76+
emit(const HeadlinesFeedError(message: 'An unexpected error occurred'));
77+
}
78+
}
79+
}
80+
81+
/// Handles [HeadlinesFeedRefreshRequested] events.
82+
///
83+
/// Fetches headlines from the repository and emits
84+
/// [HeadlinesFeedLoading], and either [HeadlinesFeedLoaded] or
85+
/// [HeadlinesFeedError] states.
86+
///
87+
/// Uses `restartable` transformer to ensure that only the latest
88+
/// refresh request is processed.
89+
Future<void> _onHeadlinesFeedRefreshRequested(
90+
HeadlinesFeedRefreshRequested event,
91+
Emitter<HeadlinesFeedState> emit,
92+
) async {
93+
emit(HeadlinesFeedLoading());
94+
try {
95+
final response = await _headlinesRepository.getHeadlines(limit: 20);
96+
emit(
97+
HeadlinesFeedLoaded(
98+
headlines: response.items,
99+
hasMore: response.hasMore,
100+
cursor: response.cursor,
101+
),
102+
);
103+
} on HeadlinesFetchException catch (e) {
104+
emit(HeadlinesFeedError(message: e.message));
105+
} catch (_) {
106+
emit(const HeadlinesFeedError(message: 'An unexpected error occurred'));
107+
}
108+
}
109+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
part of 'headlines_feed_bloc.dart';
2+
3+
/// {@template headlines_feed_event}
4+
/// Base class for all events related to the headlines feed.
5+
/// {@endtemplate}
6+
sealed class HeadlinesFeedEvent extends Equatable {
7+
/// {@macro headlines_feed_event}
8+
const HeadlinesFeedEvent();
9+
10+
@override
11+
List<Object> get props => [];
12+
}
13+
14+
/// {@template headlines_feed_fetch_requested}
15+
/// Event triggered when the headlines feed needs to be fetched.
16+
/// {@endtemplate}
17+
final class HeadlinesFeedFetchRequested extends HeadlinesFeedEvent {}
18+
19+
/// {@template headlines_feed_refresh_requested}
20+
/// Event triggered when the headlines feed needs to be refreshed.
21+
/// {@endtemplate}
22+
final class HeadlinesFeedRefreshRequested extends HeadlinesFeedEvent {}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
part of 'headlines_feed_bloc.dart';
2+
3+
/// {@template headlines_feed_state}
4+
/// Base class for all states related to the headlines feed.
5+
/// {@endtemplate}
6+
sealed class HeadlinesFeedState extends Equatable {
7+
/// {@macro headlines_feed_state}
8+
const HeadlinesFeedState();
9+
10+
@override
11+
List<Object> get props => [];
12+
}
13+
14+
/// {@template headlines_feed_initial}
15+
/// The initial state of the headlines feed.
16+
/// {@endtemplate}
17+
final class HeadlinesFeedInitial extends HeadlinesFeedState {}
18+
19+
/// {@template headlines_feed_loading}
20+
/// State indicating that the headlines feed is being loaded.
21+
/// {@endtemplate}
22+
final class HeadlinesFeedLoading extends HeadlinesFeedState {}
23+
24+
/// {@template headlines_feed_loaded}
25+
/// State indicating that the headlines feed has been loaded successfully.
26+
/// {@endtemplate}
27+
final class HeadlinesFeedLoaded extends HeadlinesFeedState {
28+
/// {@macro headlines_feed_loaded}
29+
const HeadlinesFeedLoaded({
30+
required this.headlines,
31+
required this.hasMore,
32+
this.cursor,
33+
});
34+
35+
/// The headlines data.
36+
final List<Headline> headlines;
37+
38+
/// Indicates if there are more headlines.
39+
final bool hasMore;
40+
41+
/// The cursor for the next page.
42+
final String? cursor;
43+
44+
@override
45+
List<Object> get props => [headlines, hasMore, cursor ?? ''];
46+
}
47+
48+
/// {@template headlines_feed_error}
49+
/// State indicating that an error occurred while loading the headlines feed.
50+
/// {@endtemplate}
51+
final class HeadlinesFeedError extends HeadlinesFeedState {
52+
/// {@macro headlines_feed_error}
53+
const HeadlinesFeedError({required this.message});
54+
55+
/// The error message.
56+
final String message;
57+
58+
@override
59+
List<Object> get props => [message];
60+
}

pubspec.lock

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ packages:
4141
url: "https://pub.dev"
4242
source: hosted
4343
version: "9.0.0"
44+
bloc_concurrency:
45+
dependency: "direct main"
46+
description:
47+
name: bloc_concurrency
48+
sha256: "86b7b17a0a78f77fca0d7c030632b59b593b22acea2d96972588f40d4ef53a94"
49+
url: "https://pub.dev"
50+
source: hosted
51+
version: "0.3.0"
4452
bloc_test:
4553
dependency: "direct dev"
4654
description:
@@ -598,6 +606,14 @@ packages:
598606
url: "https://pub.dev"
599607
source: hosted
600608
version: "2.1.4"
609+
stream_transform:
610+
dependency: transitive
611+
description:
612+
name: stream_transform
613+
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
614+
url: "https://pub.dev"
615+
source: hosted
616+
version: "2.1.1"
601617
string_scanner:
602618
dependency: transitive
603619
description:

pubspec.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
name: ht_main
22
description: main headlines toolkit mobile app.
3-
version: 0.5.5
3+
version: 0.6.5
44
publish_to: none
5-
repository: https://github.com/Headlines-Toolkit/ht-main
5+
repository: https://github.com/headlines-toolkit/ht-main
66
environment:
77
sdk: ^3.5.0
88

99
dependencies:
1010
bloc: ^9.0.0
11+
bloc_concurrency: ^0.3.0
1112
equatable: ^2.0.7
1213
flex_color_scheme: ^8.1.1
1314
flutter:

0 commit comments

Comments
 (0)